【Luogu】 P6662 [POI 2019] Przedszkole

题目链接

点击打开链接

题目解法

考虑把几个写法缝合

对于 S u b t a s k    1 , 2 Subtask\;1,2 Subtask1,2
因为 n n n 比较小,所以考虑指数级做法
可以发现如果每次多刷一个颜色的话转移较简单
所以列 d p dp dp 方程为 d p [ i ] [ S ] dp[i][S] dp[i][S] 表示刷了 i i i 个不同的颜色,每个颜色至少染了一个点,现在状态为 S S S 的点被染过了
转移就是按照 3 n 3^n 3n 的子集转移就可以了
需要预处理状态是否可以放在同一个颜色里
最后统计答案时只要枚举在 k k k 个颜色中选 i i i 个不同的颜色即可
时间复杂度 O ( n ∗ 3 n ) O(n*3^n) O(n3n)

struct SUBTASK12{
	int inv[20],dp[20][1<<15],okS[1<<15]; 
	bool link[20][20];
	int C(int a,int b){
		int res=1;
		for(int i=0;i<b;i++) res=(LL)res*(a-i)%P;
		return (LL)res*inv[b]%P;
	}
	void work(){
		inv[0]=1;
		for(int i=1;i<=n;i++) inv[i]=(LL)inv[i-1]*qmi(i,P-2)%P;
		for(int i=1,x,y;i<=m;i++) x=read(),y=read(),x--,y--,link[x][y]=link[y][x]=1;
		int full=(1<<n)-1;
		for(int S=0;S<=full;S++)
			for(int i=0;i<n;i++){
				if(!(S>>i&1)) continue;
				for(int j=0;j<n;j++){
					if(i==j||!(S>>j&1)) continue;
					if(link[i][j]) okS[S]=1;
				}
			}
		dp[0][0]=1;
		for(int i=0;i<n;i++)
			for(int S=0;S<=full;S++){
				if(!dp[i][S]) continue;
				int buS=full^S;
				for(int T=buS;T;T=(T-1)&buS) if(!okS[T]) (dp[i+1][S|T]+=dp[i][S])%=P;
			}
		while(t--){
			int k=read(),ans=0;
			for(int i=1;i<=n;i++) ans=(ans+(LL)dp[i][full]*C(k,i)%P)%P;
			printf("%d\n",ans);
		}
	}
}sub12;

对于 S u b t a s k    3 Subtask\;3 Subtask3
m m m 的范围比较小,所以考虑对边容斥
即容斥一些边集必须不满足两头颜色相同
具体可以用并查集维护添加边集之后块的个数

struct SUBTASK3{
	int a[25],b[25],fa[100100],cnt[100100];
	int get_father(int x){
		return x==fa[x]?x:get_father(fa[x]);
	}
	void dfs(int dep,int c,int op){
		if(dep>m){ cnt[c]+=op;return;} 
		dfs(dep+1,c,op);
		int p=get_father(a[dep]),q=get_father(b[dep]);
		if(p==q) dfs(dep+1,c,-op);
		else fa[p]=q,dfs(dep+1,c-1,-op),fa[p]=p;
	}
	void work(){
		for(int i=1;i<=m;i++) a[i]=read(),b[i]=read();
		for(int i=1;i<=n;i++) fa[i]=i;
		dfs(1,n,1);
		while(t--){
			int k=read(),ans=0;
			for(int i=1,j=1;i<=n;i++) j=(LL)j*k%P,ans=((ans+(LL)cnt[i]*j%P)%P+P)%P; 
			printf("%d\n",ans);
		}
	}
}sub3;

对于 S u b t a s k    4 Subtask\;4 Subtask4
可以发现,这个图一定是由若干个简单环组成的
考虑对每个环分开做,即询问有 n n n 个点的圆,相邻颜色不同的方案数
令此方案数为 f n f_n fn
考虑分类 c o l 1 col_1 col1 c o l 3 col_3 col3
c o l 1 = c o l 3 col_1=col_3 col1=col3 时, c o l 2 col_2 col2 k − 1 k-1 k1 种颜色选择, c o l 3 , . . . , c o l n col_3,...,col_n col3,...,coln 恰好构成一个 n − 2 n-2 n2 个点的子问题,这种情况的方案数为 ( k − 1 ) f n − 2 (k-1)f_{n-2} (k1)fn2
c o l 1 ≠ c o l 3 col_1\neq col_3 col1=col3 时, c o l 2 col_2 col2 k − 2 k-2 k2 种颜色选择, c o l 1 , c o l 3 , . . . c o l n col_1,col_3,...col_n col1,col3,...coln 恰好构成 n − 1 n-1 n1 个点的子问题,这种情况的方案数为 ( k − 2 ) f n − 1 (k-2)f_{n-1} (k2)fn1
所以 f n = ( k − 1 ) f n − 2 + ( k − 2 ) f n − 1 f_n=(k-1)f_{n-2}+(k-2)f_{n-1} fn=(k1)fn2+(k2)fn1
可以求出通项公式: f n = ( k − 1 ) n + ( − 1 ) n ∗ ( k − 1 ) f_n=(k-1)^n+(-1)^n*(k-1) fn=(k1)n+(1)n(k1)
考虑最多有 n \sqrt n n 个本质不同的环(即点数不同的环),所以每个询问只要求这 n \sqrt n n 个环的方案数就可以了
时间复杂度 O ( k n l o g n ) O(k\sqrt nlogn) O(kn logn)

struct SUBTASK4{
	int fa[100100],cnt[100100],tot[100100],pos[100100];
	int get_father(int x){
		return x==fa[x]?x:fa[x]=get_father(fa[x]);
	}
	void work(){
		for(int i=1;i<=n;i++) fa[i]=i;
		for(int i=1,a,b;i<=m;i++) a=read(),b=read(),fa[get_father(a)]=get_father(b); 
		for(int i=1;i<=n;i++) cnt[get_father(i)]++;
		for(int i=1;i<=n;i++) tot[cnt[i]]++;
		int siz=0;
		for(int i=1;i<=n;i++) if(tot[i]) pos[++siz]=i;
		while(t--){
			int k=read(),ans=1;
			for(int i=1;i<=siz;i++)
				ans=(LL)ans*qmi((qmi(k-1,pos[i])+(k-1)*(pos[i]&1?-1:1))%P,tot[pos[i]])%P;
			printf("%d\n",(ans+P)%P);
		}
	}
}sub4;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值