LOJ3235 Przedszkole 和 有限空间跳跃理论
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
容斥原理 + 子集卷积 + 拉格朗日插值 + 色多项式.
对三种子任务分别设计算法.
\(m\le 24\)
考虑容斥,暴力枚举哪些边连接的两个点颜色是相同的,用并查集维护相同颜色的点形成的连通块.
若钦定了 \(p\) 条边连接的两个点颜色相同,形成了 \(s\) 个连通块,贡献应为 \((-1)^p k^s\) .
由于每加一条边最多减少 \(1\) 个连通块,所以 \(s\) 与 \(n\) 的差不会超过 \(m\) ,维护出这个关于 \(k\) 的多项式各项系数即可.
时间复杂度 \(O(2^mm+qm)\) .
\(n\le 15\)
从 \(m\le 24\) 的做法可以注意到答案是关于 \(k\) 的 \(n\) 次多项式.
于是只需要代 \(k=1,2,3\dots,n+1\) 求出答案,就可以插值得出这个多项式.
考虑对于一个 \(k\) 如何求出答案.
染色可以看成依次为每种颜色确定点集,每种颜色的点集必须是独立集,不同颜色的点集不能相交.
记集合幂级数 \(F(x)=\sum_{S\in 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(q\sqrt n\log 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\n",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\n",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\n",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\n",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(有向无环图)的方案数,两种方案不同当且仅当存在一条边它们的方向不同。
\(n\leq 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\n",f[n][(1<<n)-1]);
return 0;
}
色多项式
其实\(k\)染色可以这样看:在子集卷积的过程中顺便带上颜色的方案数,这样因为枚举子集没有钦定编号最小的点必须选,所以多乘了一个\(k!\),最后除掉一个\(k!\)。
那么\(k\)染色相当于代入了\(k\)的下降幂。\(-1\)染色也同理可以看成代入了\(-1\)的下降幂,也就是某个阶乘乘上一个容斥系数。
从有向无环图染色理论得到启发,如果有向无环图能够\(k\)染色,就一定能够\(k+1\)染色。
其实这个染色的容斥原理跟上面题解里面的容斥原理是一样的。