Przedszkole
有一个 (n) 个点 (m) 条边的无向图,每个点从 (1) 到 (n) 编号。你有 (k) 种颜色,要给每个点染色,使得有边相连的两个点颜色不一样。求出染色方案数,对 (10^9+7) 取模。
对于 (100\%) 的数据,(1 le n le 10^5, 0 le m le min(10^5, n(n-1)/2), 1 le z le 1000, 1 le a_i, b_i le n, 1 le k_i le 10^9)。
Subtask # | 额外限制 | 分值 |
---|---|---|
1 | (n le 8, k le 8, z le 50) | (8) |
2 | (n le 15) | (26) |
3 | (m le 24) | (33) |
4 | 每个点恰好有两条边和它相连 | (33) |
题解
http://jklover.hs-blog.cf/2020/06/09/Loj-3235-Przedszkole/#more
容斥原理 + 子集卷积 + 拉格朗日插值 + 色多项式.
对三种子任务分别设计算法.
(mle 24)
考虑容斥,暴力枚举哪些边连接的两个点颜色是相同的,用并查集维护相同颜色的点形成的连通块.
若钦定了 (p) 条边连接的两个点颜色相同,形成了 (s) 个连通块,贡献应为 ((-1)^p k^s) .
由于每加一条边最多减少 (1) 个连通块,所以 (s) 与 (n) 的差不会超过 (m) ,维护出这个关于 (k) 的多项式各项系数即可.
时间复杂度 (O(2^mm+qm)) .
(nle 15)
从 (mle 24) 的做法可以注意到答案是关于 (k) 的 (n) 次多项式.
于是只需要代 (k=1,2,3dots,n+1) 求出答案,就可以插值得出这个多项式.
考虑对于一个 (k) 如何求出答案.
染色可以看成依次为每种颜色确定点集,每种颜色的点集必须是独立集,不同颜色的点集不能相交.
记集合幂级数 (F(x)=sum_{Sin I} x^S) ,其中 (I) 表示所有独立集的集合,则 (F) 子集卷积意义下 (k) 次幂全集的系数即为所求.
时间复杂度 (O(2^nn^3+qn)) .
每个点度数为 (2)
此时的图一定是由若干个互不相交的环构成的,考虑一个长度为 (l) 的环,它的色多项式为 ((k-1)^l+(-1)^l(k-1)) .
考虑容斥。首先有(k(k-1)^{l-1}),然后这样会多算进去(1)和(l)相同的。首尾相同把它们合并起来,看成长度为(l-1)的环的方案。最后到环长为(2)的时候停止。
[sum_{i=2}^l(-1)^{l-i}k(k-1)^{i-1}\ =sum_{i=2}^l(-1)^{l-i}((k-1)^i+(k-1)^{i-1})\ =(k-1)^l+(-1)^{l-2}(k-1) ]
注意长度相同的环是没有本质差别的,可以合并起来一起算.
环长种类数是 (O(sqrt n)) 的,时间复杂度 (O(qsqrt nlog n)) .
namespace Task1{
CO int N=1e5+10,M=25;
int fa[N],siz[N];
int st[M],ed[M],coef[M];
int find(int x){
return fa[x]==x?x:find(fa[x]);
}
void main(int n,int m,int q){
iota(fa+1,fa+n+1,1);
fill(siz+1,siz+n+1,1);
for(int i=0;i<m;++i) read(st[i]),read(ed[i]);
function<void(int,int,int)> dfs=[&](int x,int p,int s)->void{
if(x==m){
coef[s]=add(coef[s],p);
return;
}
int u=find(st[x]),v=find(ed[x]);
if(u!=v){
dfs(x+1,p,s);
if(siz[u]>siz[v]) swap(u,v);
fa[u]=v,siz[v]+=siz[u];
dfs(x+1,mod-p,s+1);
fa[u]=u,siz[v]-=siz[u];
}
};
dfs(0,1,0);
while(q--){
int k=read<int>(),ans=0;
int mi=max(1,n-m),pw=fpow(k,mi);
for(int i=mi;i<=n;++i){
ans=add(ans,mul(coef[n-i],pw));
pw=mul(pw,k);
}
printf("%d
",ans);
}
}
}
namespace Task2{
CO int N=15;
int nxt[N],popc[1<<N],y[N+1];
int a[N+1][1<<N],res[N+1][1<<N],tmp[N+1][1<<N];
void FWT(int a[],int n,int dir){
for(int i=0;i<n;++i)
for(int mask=0;mask<1<<n;++mask)if(mask>>i&1){
int t=a[mask^1<<i];
if(dir) t=mod-t;
a[mask]=add(a[mask],t);
}
}
void main(int n,int m,int q){
for(int i=0;i<m;++i){
int u=read<int>()-1,v=read<int>()-1;
nxt[u]|=1<<v,nxt[v]|=1<<u;
}
for(int mask=0;mask<1<<n;++mask){
popc[mask]=popc[mask>>1]+(mask&1);
bool f=1;
for(int i=0;i<n and f;++i)if(mask>>i&1)
if(mask&nxt[i]) f=0;
if(f) a[popc[mask]][mask]=1;
}
res[0][0]=1;
for(int i=0;i<=n;++i) FWT(a[i],n,0),FWT(res[i],n,0);
y[0]=0;
for(int k=1;k<=n;++k){
for(int i=0;i<=n;++i) fill(tmp[i],tmp[i]+(1<<n),0);
for(int i=0;i<=n;++i)
for(int mask=0;mask<1<<n;++mask)if(a[i][mask])
for(int j=0;i+j<=n;++j)
tmp[i+j][mask]=add(tmp[i+j][mask],mul(a[i][mask],res[j][mask]));
for(int i=0;i<=n;++i) copy(tmp[i],tmp[i]+(1<<n),res[i]);
FWT(tmp[n],n,1);
y[k]=tmp[n][(1<<n)-1];
}
while(q--){
int k=read<int>();
if(k<=n){
printf("%d
",y[k]);
continue;
}
int ans=0;
for(int i=0;i<=n;++i){
int t=y[i];
for(int j=0;j<=n;++j)if(j!=i){
t=mul(t,k+mod-j);
t=mul(t,fpow(i+mod-j,mod-2));
}
ans=add(ans,t);
}
printf("%d
",ans);
}
}
}
namespace Task3{
CO int N=1e5+10;
int vis[N],siz[N];
vector<int> to[N];
int a[N],b[N];
int dfs(int x){
vis[x]=1;
int s=1;
for(int y:to[x])if(!vis[y]) s+=dfs(y);
return s;
}
void main(int n,int m,int q){
for(int i=1;i<=n;++i){
int u=read<int>(),v=read<int>();
to[u].push_back(v),to[v].push_back(u);
}
int tot=0;
for(int i=1;i<=n;++i)if(!vis[i]) siz[++tot]=dfs(i);
sort(siz+1,siz+tot+1);
int c=0;
for(int l=1,r=1;l<=tot;l=r){
a[++c]=siz[l];
for(;r<=n and siz[r]==siz[l];++r) ++b[c];
}
while(q--){
int k=read<int>(),ans=1;
for(int i=1;i<=c;++i){
int t=fpow(k-1,a[i]);
if(a[i]&1) t=add(t,mod-(k-1));
else t=add(t,k-1);
ans=mul(ans,fpow(t,b[i]));
}
printf("%d
",ans);
}
}
}
int main(){
int n=read<int>(),m=read<int>(),q=read<int>();
if(m<=24) Task1::main(n,m,q);
else if(n<=15) Task2::main(n,m,q);
else Task3::main(n,m,q);
return 0;
}
有限空间跳跃理论
给出一个无向连通图,求给每条边定向后是DAG(有向无环图)的方案数,两种方案不同当且仅当存在一条边它们的方向不同。
(nleq 20)。
题解
https://blog.csdn.net/sadnohappy/article/details/89389217
设(f(S))表示集合(S)的点在DAG上的方案数,转移时枚举一个独立集(T)表示度数为(0)的点,大概转移时这样
这个系数是怎么来的呢?考虑每次都可以只加一个点,但是如果两个点独立的话,那么他们两个谁先谁后没有区别,所以算重了。归纳一下容斥系数应该是((-1)^{|T|-1})。
直接枚举是(3^n)的,需要子集卷积优化。(O(2^nn^2))。
CO int N=20;
int a[N];
int f[N+1][1<<N],g[N+1][1<<N];
void FWT(int a[],int n,int dir){
for(int i=0;i<n;++i)
for(int mask=0;mask<1<<n;++mask)if(mask>>i&1){
int t=a[mask^1<<i];
if(dir) t=mod-t;
a[mask]=add(a[mask],t);
}
}
int main(){
freopen("jump.in","r",stdin),freopen("jump.out","w",stdout);
int n=read<int>();
for(int m=read<int>();m--;){
int u=read<int>()-1,v=read<int>()-1;
a[u]|=1<<v,a[v]|=1<<u;
}
for(int mask=1;mask<1<<n;++mask){
bool flag=1;
for(int i=0;i<n and flag;++i)if(mask>>i&1)
if(mask&a[i]) flag=0;
if(!flag) continue;
g[popcount(mask)][mask]=add(g[popcount(mask)][mask],popcount(mask)%2==1?1:mod-1);
}
for(int i=0;i<=n;++i) FWT(g[i],n,0);
f[0][0]=1;
for(int i=1;i<=n;++i){
FWT(f[i-1],n,0);
for(int j=0;j<=i-1;++j)for(int mask=0;mask<1<<n;++mask)
f[i][mask]=add(f[i][mask],mul(f[j][mask],g[i-j][mask]));
FWT(f[i],n,1);
for(int mask=0;mask<1<<n;++mask)
if(popcount(mask)!=i) f[i][mask]=0;
}
printf("%d
",f[n][(1<<n)-1]);
return 0;
}
色多项式
其实(k)染色可以这样看:在子集卷积的过程中顺便带上颜色的方案数,这样因为枚举子集没有钦定编号最小的点必须选,所以多乘了一个(k!),最后除掉一个(k!)。
那么(k)染色相当于代入了(k)的下降幂。(-1)染色也同理可以看成代入了(-1)的下降幂,也就是某个阶乘乘上一个容斥系数。
从有向无环图染色理论得到启发,如果有向无环图能够(k)染色,就一定能够(k+1)染色。
其实这个染色的容斥原理跟上面题解里面的容斥原理是一样的。