2024.12.14 测试

T1 游戏设计

link
题意
你要设计所有长度为 K K K 的字符串,每个字符是 A,B,C,D 中选择一个,显然共有 4 K 4^K 4K 个不同的字符串,你要给每个字符串都分配一种颜色,不同的字符串可以分配相同的颜色,颜色分配过程是由你来决定的。
首先 B e s s i e Bessie Bessie 会输入一个长度是 K K K 的字符串 S S S S S S 是上面 4 k 4^k 4k 个字符串中的其中一个),然后 B e s s i e Bessie Bessie 每一步操作是如下两种选择之一:

  1. 交换当前字符串 S S S 的相邻的两个字符。(第一个字符和最后一个字符不相邻)
  2. 如果当前字符串 S S S 含有子串 b [ i ] b[i] b[i],那么可以把该子串替换成字符串 c [ i ] c[i] c[i] 。其中 b b b 数组和 c c c 数组都是字符串数组。(输入数据给出)

如果 B e s s i e Bessie Bessie 操作若干次后,使字符串 S S S 能变成字符串 T T T T ≠ S T \not= S T=S),且 T T T S S S 颜色相同,那么 B e s s i e Bessie Bessie 胜利。你希望无论 B e s s i e Bessie Bessie 输入的字符串 S S S 是什么,都不可能胜利,那么至少需要多少种不同的颜色才能实现。

多测, 1 ≤ g r o u p ≤ 10 1 \le group \le 10 1group10 1 ≤ K ≤ 30 1 \le K \le 30 1K30 1 ≤ N ≤ 50 1 \le N \le 50 1N50 N N N 表示数组 c c c d d d 的长度, c c c d d d 长度相等)

思路
考虑将可以互相转换的字符串连边,那么答案就是将整个图缩点后得到的 D A G DAG DAG 上的最长链。但是图中的点数达 4 K 4^K 4K ,显然不行。
但是有一个明显的结论,如果两个字符串 4 4 4 种字母出现的次数都是一样的,显然可以通过不停交换相邻字符来变得相等。
所以我们可以用一个四元组 ( a , b , c , d ) (a,b,c,d) (a,b,c,d) 为点表示 所有由 a a aA b b bB c c cC d d dD 组成的字符串,点权为 K ! a ! b ! c ! d ! \frac{K!}{a!b!c!d!} a!b!c!d!K!。最多只有 ( 30 + 4 − 1 4 − 1 ) = 5456 \binom{30+4-1}{4-1} = 5456 (4130+41)=5456 个点。
将可以转化的四元组互相连边,缩点 + + + D A G DAG DAG 上最长链。
时间复杂度大概为 O ( K 3 ) O(K^3) O(K3)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5460;

ll Get_val(int sa,int sb,int sc,int sd,int n){
	__int128 s=1;
	for(int i=1;i<=n;i++) s*=i;
	for(int i=1;i<=sa;i++) s/=i;
	for(int i=1;i<=sb;i++) s/=i;
	for(int i=1;i<=sc;i++) s/=i;
	for(int i=1;i<=sd;i++) s/=i;
	return s;
}

int idx,head[maxn];
struct EDGE{ int v,next; }e[maxn*50];
void Insert(int u,int v){
	e[++idx]={v,head[u]};
	head[u]=idx;
}

int dfn[maxn],low[maxn],tim,scc_cnt,scc[maxn];
ll sccs[maxn];//!!!longlong
stack<int>stk;
struct NODE{ int sa,sb,sc,sd; ll val; }pl[maxn];
void Tarjan(int u){
	dfn[u]=low[u]=++tim;
	stk.push(u);
	for(int i=head[u];i;i=e[i].next){
		int v=e[i].v;
		if(!dfn[v]){
			Tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!scc[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		scc_cnt++;
		int p=0;
		do{
			p=stk.top();
			stk.pop();
			scc[p]=scc_cnt;
			sccs[scc_cnt]+=pl[p].val;
		}while(p!=u);
	}
}

int du[maxn];
ll f[maxn];
queue<int>q;
void Topo(){
	while(!q.empty()) q.pop();
	for(int i=1;i<=scc_cnt;i++){
		f[i]=sccs[i];
		if(!du[i]) q.push(i);
	}
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].v;
			du[v]--;
			f[v]=max(f[v],f[u]+sccs[v]);
			if(!du[v]) q.push(v);
		}
	} 
}

int vl[31][31][31][31];
struct NODE2{ int b[5],c[5]; }a[55];
struct EDGE2{ int x,y; }e2[maxn*50];
string st;
int main(){
	int group; cin>>group;
	while(group--){
		int K,n; cin>>K>>n;
		for(int i=1;i<=n;i++){
			cin>>st;
			for(int j=0;j<st.size();j++)
				a[i].b[st[j]-'A']++;
		}
		for(int i=1;i<=n;i++){
			cin>>st;
			for(int j=0;j<st.size();j++)
				a[i].c[st[j]-'A']++;
		}
		int tot=0;
		for(int i=0;i<=K;i++)
			for(int j=0;j<=K-i;j++)
				for(int k=0;k<=K-i-j;k++){
					int q=K-i-j-k;
					pl[++tot]={i,j,k,q,Get_val(i,j,k,q,K)};
					vl[i][j][k][q]=tot;
				}
		int cnt=0;
		for(int i=1;i<=tot;i++)
			for(int j=1;j<=n;j++){
				int na=pl[i].sa-a[j].b[0],nb=pl[i].sb-a[j].b[1],nc=pl[i].sc-a[j].b[2],nd=pl[i].sd-a[j].b[3];
				if(na>=0&&nb>=0&&nc>=0&&nd>=0){
					na+=a[j].c[0],nb+=a[j].c[1],nc+=a[j].c[2],nd+=a[j].c[3];
					if(vl[na][nb][nc][nd]!=i){
						Insert(i,vl[na][nb][nc][nd]);
						e2[++cnt]={i,vl[na][nb][nc][nd]};
					} 
				}
			}
		while(!stk.empty()) stk.pop();
		for(int i=1;i<=tot;i++)
			if(!dfn[i]) Tarjan(i);
		idx=0;
		for(int i=1;i<=tot;i++)
			head[i]=0;
		for(int i=1;i<=cnt;i++){
			int x=e2[i].x,y=e2[i].y;
			if(scc[x]!=scc[y]){
				Insert(scc[x],scc[y]);
				du[scc[y]]++;
			}
		}
		Topo();
		ll ans=0;
		for(int i=1;i<=scc_cnt;i++)
			ans=max(ans,f[i]);
		cout<<ans<<"\n";
		for(int i=1;i<=n;i++)
			a[i].b[0]=a[i].b[1]=a[i].b[2]=a[i].b[3]=a[i].c[0]=a[i].c[1]=a[i].c[2]=a[i].c[3]=0;
		idx=0;
		for(int i=1;i<=tot;i++)
			dfn[i]=low[i]=head[i]=scc[i]=0;
		for(int i=1;i<=scc_cnt;i++)
			f[i]=du[i]=sccs[i]=0;
		scc_cnt=tim=0;
	}
	return 0;
}

T2 锦标赛([ARC093F] Dark Horse)

link
题意
2 N 2^N 2N 个人,按照满二叉树的形态进行淘汰赛,一开始的排列顺序为所有 ( 2 N ) ! (2^N)! (2N)! 个排列之一。你是第 1 1 1 个人,已知每一对人之间的实力关系,具体地说:

  • 给出 M M M 个人 A 1 ∼ A M A_1 \sim A_M A1AM,这 M M M 个人都打得过你。
  • 你打得过除了这 M M M 个人之外的所有其他人。
  • 对于剩下的情况(你不参与的情况),编号小的人胜利。
    问你在所有的 ( 2 N ) ! (2^N)! (2N)! 种情况中,有多少种情况可以取得最终胜利,答案对 10 9 + 7 {10}^9 + 7 109+7 取模。

1 ≤ N ≤ 16 1 \le N \le 16 1N16 0 ≤ M ≤ 16 0 \le M \le 16 0M16 2 ≤ A i ≤ 2 N 2 \le A_i \le 2^N 2Ai2N A i < A i + 1 A_i < A_{i + 1} Ai<Ai+1

思路
首先,知道我的位置不重要,那么就钦定我在1号位置,最后答案乘 2 N 2^N 2N 即可。
我们可以让比赛顺序变成如下图所示:
在这里插入图片描述
即,我与区间 [ 2 , 2 ] , [ 3 , 4 ] , [ 5 , 8 ] , . . . . . . , [ 2 n − 1 + 1 , 2 n ] [2,2] , [3,4] , [5,8] , ...... , [2^{n-1}+1,2^n] [2,2],[3,4],[5,8],......,[2n1+1,2n] 比赛,都要获胜。那么每个区间中的最小值都不能为 A A A 中的数。
把题意简化为:将 [ 2 , 2 N ] [2,2^N] [2,2N] 中的元素分成 N N N 个大小为 2 i 2^i 2i ( i ∈ [ 0 , n − 1 ] i \in [0,n-1] i[0,n1]) 的集合,并保证每个集合中的最小值都不在 A A A 中的方案数。
这样有点复杂,考虑容斥,记 F ( s ) F(s) F(s) 表示 s s s 二进制下为 1 1 1 的位置所对应的组里面最小值为 A A A 中的元素时的方案数。(设第 G 组: [ 2 G − 1 , 2 G + 1 ] [2^G-1,2^{G+1}] [2G1,2G+1])那么答案为:
a n s = 2 n × ( ∑ s ( − 1 ) p o p c o u n t ( s ) F ( s ) ) ans=2^n \times (\sum_{s} (-1)^{popcount(s)}F(s)) ans=2n×(s(1)popcount(s)F(s))
考虑如何求 F ( s ) F(s) F(s) 。(不合法的集合指的是集合里面的最小值是 A A A 中的元素)
当我们枚举 A A A 中的数并用它来使集合不合法的时候,集合剩下的 个数都要大于这个数,这会有后效性,那么将 A A A 中的数从大到小排序,这样前面考虑过的数一定大于后面被考虑的数,就可以避免后效性。
f i , j f_{i,j} fi,j 表示考虑到第 i i i 个被标记的数, j j j 二进制下为 1 1 1 的集合已经不合法,考虑转移:(假定当前考虑到 A A A 中的第 i i i 个数 A i A_i Ai

  1. 不用 A i A_i Ai 去不合法化任何集合,即 f i , j = f i − 1 , j f_{i,j} = f_{i-1,j} fi,j=fi1,j
  2. A i A_i Ai 去不合法化大小为 2 k 2^k 2k 的集合,此时需要另选 2 k − 1 2^k −1 2k1 个数,可供选择的数有 2 N − j − A i 2^N - j - A_i 2NjAi 个 (因为选择的数要大于 A i A_i Ai ,如认为不合法的集合已经填满,则有 j j j 个数也已经用过,所以也不能选这 j j j 个数),并且集合中的数可以随意排列,转移为 f i , j ∣ 2 k = f i , j × ( 2 N − j − A i 2 k − 1 ) × ( 2 k ) ! f_{i,j|2^k} = f_{i,j} \times \binom{2^N-j-A_i}{2^k-1} \times (2^k)! fi,j2k=fi,j×(2k12NjAi)×(2k)!

所以, F ( s ) = f M , s × ( N − 1 − s ) ! F(s) = f_{M,s} \times (N-1-s)! F(s)=fM,s×(N1s)! 。(若到最后还有空的集合,就直接将剩下的数乱填即可)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxm=20,maxn=65540,mod=1e9+7;

ll Pow_(ll x,ll y){
	ll s=1;
	while(y){
		if(y&1) (s*=x)%=mod;
		(x*=x)%=mod;
		y>>=1;
	}
	return s;
}

ll fac[maxn],inv_fac[maxn];
ll C(ll m,ll n){
	if(m<0||n<0||m<n) return 0ll;//!!! 
	return fac[m]*inv_fac[n]%mod*inv_fac[m-n]%mod;
}

int Cnt_binary(int x){
	int s=0;
	while(x){
		if(x&1) s++;
		x>>=1;
	}
	return s;
}

int a[maxm];
ll f[maxm][maxn];
int main(){
	int n,m; cin>>n>>m; int N=(1<<n);
	for(int i=1;i<=m;i++)
		cin>>a[i];
	sort(a+1,a+m+1,[](int a,int b){ return a>b; });
	fac[0]=inv_fac[0]=1;
	for(int i=1;i<=N;i++){
		fac[i]=fac[i-1]*i%mod;
		inv_fac[i]=Pow_(fac[i],mod-2);
	}
	f[0][0]=1;//f[i][j]:考虑到第 i 个我打不过的人,j 的二进制位为1的集合已经不合法的方案数 
	for(int i=1;i<=m;i++)
		for(int j=0;j<N;j++){
			(f[i][j]+=f[i-1][j])%=mod;
			for(int k=0;k<n;k++)
				if(!((1<<k)&j)) (f[i][j|(1<<k)]+=f[i-1][j]*C(N-j-a[i],(1<<k)-1)%mod*fac[1<<k]%mod)%=mod;
		}
	ll ans=0;
	for(int i=0;i<N;i++){
		if(Cnt_binary(i)&1) ans-=f[m][i]*fac[N-1-i]%mod,(ans+=mod)%=mod;
		else (ans+=f[m][i]*fac[N-1-i]%mod)%=mod;
	}
	cout<<ans*Pow_(2,n)%mod;
	return 0;
}

总结

不想总结~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值