【洛谷6914】[ICPC2015 WF] Tours(非树边随机边权,树边边权异或)
- 给定一张\(n\)个点\(m\)条边的图,要求用\(k\)种颜色给所有边染色,使得每个简单环中每种颜色的边出现次数相同。
- 求所有可以达成条件的\(k\)。
- \(n,m\le2000\)
想出了一个\(O(mlogn)\)的做法,看到这么小的数据范围懵了半天,最后鼓起勇气写了一发结果过了。
始终在一起的边集
显然我们只需求出一个最大的\(k\),那么答案就应该是它的全部因数。
粗略一想答案应该是所有环长的\(gcd\),但我们发现环与环之间可能存在交集,而真正的答案应该是所有环长以及所有交集大小的总\(gcd\)。
这等价于把每个环都拆解成若干部分,因此我们要求的实际上就是所有始终在一起的边集大小的\(gcd\)。
经典非树边随机边权
随便找出图中的一棵生成树,给每条非树边随机一个权值,令每条树边的权值为覆盖它的所有非树边边权的异或和。
这样一来,始终在一起的边集就是边权相同的边集。
直接开个\(map\)存下每种边权的边数,然后求个\(gcd\)即可。
代码:\(O(mlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define ull unsigned long long
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,ee,lnk[N+5];struct edge {int to,nxt;}e[2*N+5];
I int gcd(CI x,CI y) {return y?gcd(y,x%y):x;}
int d,dfn[N+5],ct;ull a[N+5],s[N+5];map<ull,int> p;I void dfs(CI x,CI lst)//dfs扫一遍
{
#define R() ((1ull*rand()*rand()*rand()*rand())^(1ull*rand()*rand()*rand()*rand()))//随机一个边权
dfn[x]=++d;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(dfn[e[i].to]
?dfn[e[i].to]<dfn[x]&&(++p[s[++ct]=R()],a[e[i].to]^=s[ct],a[x]^=s[ct]):(dfs(e[i].to,x),a[x]^=a[e[i].to]));//非树边随机边权,树边继续dfs
a[x]&&++p[a[x]];//统计每种边权的出现次数(a[x]=0表示不在环上,忽略)
}
int main()
{
RI i,x,y;for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
RI g=0;for(i=1;i<=n;++i) dfn[i]?a[i]&&(g=gcd(g,p[a[i]])):(dfs(i,0),0);//统计树边所在边集gcd
for(i=1;i<=ct;++i) g=gcd(g,p[s[i]]);for(i=1;i<=g;++i) !(g%i)&&printf("%d ",i);return 0;//统计非树边所在边集gcd;所有因数都是答案
}
待到再迷茫时回头望,所有脚印会发出光芒