P6914 - Tours 题解
我们称若干个不相交的简单环的并叫做复合环,也就是每个点为偶度的边集。如果每个简单环都满足条件,那么所有复合环显然也都满足条件。考虑两个简单环 \(A,B\),那么 \(A\oplus B\) 显然必定是复合环,它也要满足条件。我们将 \(A\cup B\) 拆成三个不相交的部分:\(S_1=A\cap B,S_2=A-A\cap B,S_3=B-A\cap B\),那么 \(A,B,A\oplus B\) 都由其中恰好两个组成。直觉告诉我们 \(S_1,S_2,S_3\) 这三个边集都要满足条件。显然它们满足条件是 \(A,B,A\oplus B\) 的充分条件,但是必不必要呢?反证,不妨设 \(S_1\) 中某个颜色数量偏少,那么 \(S_2,S_3\) 的该颜色都要偏多,那么 \(S_2\cup S_3\) 该颜色必定偏多,不行,必要性得证。
于是简单环 \(A,B\) 就可以等价地拆成 \(S_1,S_2,S_3\) 这三个不相交的边集来完成条件。然后还可以对其它的边集对(此时不只有简单环了)进行这样拆,直到场上剩下的所有边集都互不相交,就是每条边恰好属于一个类。
为什么要拆成不相交的边集们呢。因为这样有独立性,舒服啊。答案显然是最终每个类的大小的 gcd(不属于任何简单环的边类除外,很好理解吧)。
两条边在同一个类中的充要条件显然是它们所在简单环集合相等。这玩意好像还有个学名叫切边等价。考虑把所有这样的切边等价类求出来,好像还挺可做的,毕竟虽然简单环个数是指数级的,但是我们只需要判断两条边所在简单环集合是否相等,不需要真的求出来简单环集合。
数据范围给的是平方的,正好够我们对每对边判断它们切边等价。怎么判断?边所在简单环,想到边双理论。一条边不在任何简单环上当且仅当它是割边。而如果不是割边,那么把它割掉,虽然图还连通,但是它所在的所有简单环都消失。如果此时我们再跑 tarjan 判另一条边是否割边,如果是割边,说明它所在的简单环,割掉的边都在。这说明一个的所在简单环集合包含于另一个。而如果反过来判一下,不就可以确定是否相等了吗,不就完事了吗!复杂度平方,跑 \(m\) 遍 tarjan,对每对边记录包含性,最后再 dfs 或者并查集划分出切边等价类。
code
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=2010,M=2010;
int n,m;
int a[M],b[M],t[N][N];
vector<int> nei[N];
bool to[N][N];
int dfn[N],low[N],nowdfn;
bool cut[M];
int ban;
void tar(int x,int fa=0){
dfn[x]=low[x]=++nowdfn;
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(x==a[ban]&&y==b[ban]||x==b[ban]&&y==a[ban])continue;
if(y==fa){fa=-1;continue;}
if(!dfn[y]){
tar(y,x),low[x]=min(low[x],low[y]);
if(dfn[x]<low[y])cut[t[x][y]]=true;
}
else low[x]=min(low[x],dfn[y]);
}
}
struct ufset{
int fa[M];
ufset(){memset(fa,0,sizeof(fa));}
int root(int x){return fa[x]?fa[x]=root(fa[x]):x;}
void mrg(int x,int y){
x=root(x),y=root(y);
if(x!=y)fa[x]=y;
}
}ufs;
int gcd(int x,int y){return y?gcd(y,x%y):x;}
int cnt[M];
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
a[i]=x,b[i]=y;
nei[x].pb(y),nei[y].pb(x);
t[x][y]=t[y][x]=i;
}
for(int i=1;i<=m;i++){
ban=i;
memset(dfn,0,sizeof(dfn)),nowdfn=0;
memset(cut,0,sizeof(cut));
for(int j=1;j<=n;j++)if(!dfn[j])tar(j);
for(int j=1;j<=m;j++)if(cut[j])to[i][j]=true;
}
for(int i=1;i<=m;i++)for(int j=1;j<=m;j++)if(to[i][j]&&to[j][i])ufs.mrg(i,j);
ban=0;
memset(dfn,0,sizeof(dfn)),nowdfn=0;
memset(cut,0,sizeof(cut));
for(int i=1;i<=n;i++)if(!dfn[i])tar(i);
for(int i=1;i<=m;i++)cnt[ufs.root(i)]++;
int ans=0;
for(int i=1;i<=m;i++)if(!cut[i])ans=gcd(ans,cnt[i]);
for(int i=1;i<=m;i++)if(ans%i==0)cout<<i<<" ";
return 0;
}