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\)染色。
其实这个染色的容斥原理跟上面题解里面的容斥原理是一样的。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
· Ai满嘴顺口溜,想考研?浪费我几个小时
· Browser-use 详细介绍&使用文档
· 软件产品开发中常见的10个问题及处理方法