【题解】[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-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);
}
}
}