Loading

【题解】[POI 2019] Przedszkole

数数综合题。

对于子任务 \(1\) 直接枚举所有点的颜色,同时记录一下当前是否满足条件,时间复杂度 \(\mathcal{O}(nk^n)\)

对于子任务 \(2\) 点数很小,考虑状压 \(\rm DP\)

我们定义状态 \(f[i][S]\) 表示使用恰好 \(i\) 种颜色,其中状态 \(S\) 中的点已经被染色的方案数。

初值 \(f[0][0]=1\) ,转移 \(f[i][j]=\sum f[i-1][j \oplus k]\) ,其中 \(k\)\(j\) 的非空子集且 \(k\) 中的点两两之间没有边。

最后再从 \(k\) 种颜色中选出 \(i\) 种颜色,答案为 \(\sum\limits_{1\le i\le n} \dbinom{k}{i}f[i][2^n-1]\)

枚举子集的复杂度是 \(\mathcal{O}(3^n)\) ,所以时间复杂度是 \(\mathcal{O}(n3^n+Tn^2)\)

namespace task1{
	int f[16][1<<15],ban[16],v[1<<15];
	int C(int x,int y){
		if(x<0||y<0||x<y)return 0;
		int cur=1;rep(i,1,y)cur=1LL*cur*(x-i+1)%P*Pow(i,P-2)%P;
		return cur;
	}
	void main(){
		int T;scanf("%d",&T);
		f[0][0]=1;int w=1<<n;
		rep(i,1,m){
			int x,y;scanf("%d%d",&x,&y);
			ban[x-1]|=1<<(y-1),ban[y-1]|=1<<(x-1);
		}
		rep(i,0,w-1)rep(j,0,n-1)if(((i>>j)&1)&&(ban[j]&i))v[i]=1;
		rep(i,1,n)rep(j,1,w-1)for(int k=j;k;k=(k-1)&j)
			if(!v[k])f[i][j]=(f[i][j]+f[i-1][j^k])%P;
		while(T--){
			int x,ans=0;scanf("%d",&x);
			rep(i,1,n)ans=(ans+1LL*f[i][w-1]*C(x,i))%P;
			printf("%d\n",ans);
		}
	}
}

对于子任务 \(3\) ,边数很小 。

根据数据范围,考虑 \(2^m\) 算法,除了状压 \(\rm DP\) ,直接容斥的复杂度也是 \(2^m\) 的。

所以我们钦定哪些边的两端相等,将答案乘以容斥系数即可。

具体我们可以用并查集维护,注意这里并查集不能路径压缩。

时间复杂度 \(\mathcal{O}(m2^m+Tm\log P)\)

namespace task2{
	int u[N],v[N],fa[N],c[N];
	inline int get(int x){
		while(x!=fa[x])x=fa[x];
		return x;
	}
	void dfs(int x,int cnt,int op){
		if(x>m)c[cnt]+=op;
		else{
			int p=get(u[x]),q=get(v[x]);
			if(p==q){
				dfs(x+1,cnt,op),dfs(x+1,cnt,-op);
			}
			else{
				dfs(x+1,cnt,op);fa[p]=q;dfs(x+1,cnt-1,-op);fa[p]=p;
			}
		}
	}
	void main(){
		int T;scanf("%d",&T);
		rep(i,1,m)scanf("%d%d",&u[i],&v[i]);
		rep(i,1,n)fa[i]=i;dfs(1,n,1);
		while(T--){
			int k,ans=0;scanf("%d",&k);
			rep(i,max(1,n-m),n)ans=(ans+1LL*c[i]*Pow(k,i))%P;
			printf("%d\n",(ans+P)%P);
		}
	}
}

对于子任务 \(4\) ,图退化成若干个环。

不同的环之间互不相干,考虑对每个环单独计算。

我们定义状态 \(f[i]\) 表示长度为 \(i\) 的环染色方案数。

有初始状态 \(f[3]=k(k-1)(k-2)\)

转移:如果第 \(n-1\) 个点与第一个点颜色相同,第 \(n\) 个点有 \(k-1\) 种取值,方案数为 \((k-1)f[n-2]\) ,否则第 \(n\) 个点有 \(k-2\) 种取值,方案数为 \((k-2)f[n-1]\)

\[f[n]=(k-2)f[n-1]+(k-1)f[n-2] \]

这已经可以利用矩阵快速计算,也可以解递推式得到 \(f[n]=(k-1)^n+(-1)^n(k-1)\)

环的个数可能很多,但是本质不同的环不超过 \(\sqrt{n}\) ,所以我们将本质相同的环合并即可。

时间复杂度 \(\mathcal{O}(T\sqrt{n}\log P)\)

namespace task3{
	int h[N],tot,v[N],c[N],sta[N],top;
	struct edge{int to,nxt;}e[N<<1];
	void add(int x,int y){e[++tot].nxt=h[x];h[x]=tot;e[tot].to=y;}
	int dfs(int x){
		v[x]=1;int sz=1;
		for(int i=h[x];i;i=e[i].nxt)if(!v[e[i].to])sz+=dfs(e[i].to);
		return sz;
	}
	inline int calc(int x,int y){
		return (Pow(x-1,y)+(y&1?-1:1)*(x-1))%P;
	}
	void main(){
		int T;scanf("%d",&T);
		rep(i,1,m){
			int x,y;scanf("%d%d",&x,&y);
			add(x,y);add(y,x);
		}
		rep(i,1,n)if(!v[i])c[dfs(i)]++;
		rep(i,1,n)if(c[i])sta[++top]=i;
		while(T--){
			int k,ans=1;scanf("%d",&k);
			rep(i,1,top){
				ans=1LL*ans*Pow(calc(k,sta[i]),c[sta[i]])%P;
			}
			printf("%d\n",(ans+P)%P);
		}
	}
}
posted @ 2021-06-07 15:10  7KByte  阅读(126)  评论(0编辑  收藏  举报