IOI2021集训队作业277BK Tours
给出一个无向图,你需要给每条边染\(k\)种色,使得:对于每个环,这\(k\)种色的出现次数相同。
问有哪些合法的\(k\)。
\(n,m\le 2000\)
假如环不相交,那么可以将每个环上的边分成一个集合。把集合大小求\(\gcd\)即可。
当两个环相交的时候,发现原本的两个集合\(S_1,S_2\)要分成三类:\(S_1\bigcap S_2,S_1\setminus S_1\bigcap S_2,S_2\setminus S_1\bigcap S_2\)。
推广到更多的环,可以发现:两条边在同一个集合,当且仅当包含它们的环的集合相同。
问题变为求出所有的集合(注意忽略桥边)。
到了这里我卡住了。%%%ymq秒掉。
判断两条边\(a,b\)是否在同一集合(称其为等价类)。先删\(a\)看看\(b\)是否在环中,再删\(b\)看看\(a\)是否在环中。如果都不在,那么它们为等价类。时间\(O(m^2)\)。
还有更妙的做法:建dfs树,对于非树边随机赋一个权值,将其跨过的树边的权值都异或上这个权值。最终权值相同的为等价类。
简要说明正确性:首先两条非树边不可能为等价类,因为非树边可以和树边组成环;其次不需要考虑两条或以上非树边所组成的环,因为这个环一定可以拆成多个只有一条非树边的环,并且每个环覆盖的树边为这个环覆盖的树边的子集。
时间\(O(m+m\lg m)\)(后面这个\(\lg\)是map
)
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 2005
int gcd(int a,int b){
int k;
while (b)
k=a%b,a=b,b=k;
return a;
}
int n,m;
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
void link(int u,int v){
e[ne]={v,last[u]};
last[u]=e+ne++;
}
int era;
int bz[N][N];
int dfn[N],low[N],cnt;
void dfs(int x,int fa,int bz[]){
low[x]=dfn[x]=++cnt;
for (EDGE *ei=last[x];ei;ei=ei->las){
if (ei-e>>1==era || ei->to==fa)
continue;
if (!dfn[ei->to]){
dfs(ei->to,x,bz);
low[x]=min(low[x],low[ei->to]);
if (low[ei->to]<=dfn[x])
bz[ei-e>>1]=1;
}
else{
low[x]=min(low[x],dfn[ei->to]);
bz[ei-e>>1]=1;
}
}
}
int dsu[N],c[N];
int getdsu(int x){return dsu[x]==x?x:dsu[x]=getdsu(dsu[x]);}
void work(int x){
era=x;
memset(dfn,0,sizeof(int)*(n+1));
cnt=0;
for (int i=1;i<=n;++i)
if (!dfn[i])
dfs(i,0,bz[x]);
}
int main(){
freopen("in.txt","r",stdin);
freopen("right.txt","w",stdout);
scanf("%d%d",&n,&m);
for (int i=0;i<m;++i){
int u,v;
scanf("%d%d",&u,&v);
link(u,v),link(v,u);
}
work(m);
for (int i=0;i<m;++i)
if (bz[m][i])
work(i);
// for (int i=0;i<=m;++i,printf("\n"))
// for (int j=0;j<m;++j)
// printf("%d ",bz[i][j]);
for (int i=0;i<m;++i)
dsu[i]=i;
for (int i=0;i<m;++i)
if (bz[m][i])
for (int j=0;j<m;++j)
if (bz[m][j] && bz[i][j]==0 && bz[j][i]==0)
dsu[getdsu(i)]=getdsu(j);
for (int i=0;i<m;++i)
if (bz[m][i])
c[getdsu(i)]++;
int g=0;
for (int i=0;i<m;++i)
if (bz[m][i])
g=gcd(g,c[i]);
printf("1");
for (int i=2;i<=m;++i)
if (g%i==0)
printf(" %d",i);
return 0;
}
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <map>
#define N 2005
#define ll long long
#define irand (rand()*RAND_MAX+rand())
#define lrand ((ll)irand*RAND_MAX*RAND_MAX+irand)
int gcd(int a,int b){
int k;
while (b)
k=a%b,a=b,b=k;
return a;
}
int n,m;
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
void link(int u,int v){
e[ne]={v,last[u]};
last[u]=e+ne++;
}
ll v[N],s[N];
map<ll,int> ma;
int vis[N],ins[N];
void dfs(int x,int fa=0){
vis[x]=1;
ins[x]=1;
for (EDGE *ei=last[x];ei;ei=ei->las){
if (ei->to==fa) continue;
if (!vis[ei->to]){
dfs(ei->to,x);
s[x]^=v[ei-e>>1]=s[ei->to];
}
else if (ins[ei->to]){
s[ei->to]^=v[ei-e>>1]=lrand;
s[x]^=v[ei-e>>1];
}
}
ins[x]=0;
}
int main(){
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
srand(time(0));
scanf("%d%d",&n,&m);
for (int i=1;i<=m;++i){
int u,v;
scanf("%d%d",&u,&v);
link(u,v),link(v,u);
}
for (int i=1;i<=n;++i)
if (!vis[i])
dfs(i);
for (int i=0;i<m;++i)
if (v[i])
ma[v[i]]++;
int g=0;
for (auto p=ma.begin();p!=ma.end();++p)
g=gcd(g,p->second);
printf("1");
for (int i=2;i<=m;++i)
if (g%i==0)
printf(" %d",i);
return 0;
}