一、题目
二、解法
首先考虑如果所有的环不交,那么答案就是环长度 \(\gcd\) 的所有因子。想一想相交的情况复杂在哪里?我们的常规方法是按照 \(\tt dfs\) 的顺序考察返祖边构成的环,但是这些环可能会组合生成新环,进而产生更多限制:
上图所展示的三部分分别表示:两个环的交、第一个环减去交的部分、第二个环减去交的部分。那么所有组合出来的环合法的充要条件是:这些基本部分分别合法(充分性显然,必要性通过反证法可以说明)
我们维护一个集族 \(S\),初始时包含图上所有环,每次从中取出两个相交的边集,再放回操作后三个不交的部分。那么最后的答案就是所有边集大小 \(\tt gcd\) 的因子。但是我们不可能暴力进行这个过程,只能考察最后结果的性质。
关键的 \(\tt observation\) 是:两条边 \(e_1,e_2\) 最后处在同一个边集的充要条件是,初始时它们在同一个环中,并且对于初始时候的所有环,它们要不然同时包含 \(e_1,e_2\),要不然不含有 \(e_1,e_2\) 的任意一个。
那么我们可以枚举 \(e_1\),统计 \(e_2\) 的个数,就得到了这个边集的大小。那么 \(e_1,e_2\) 初始一定是非割边,并且删去 \(e_1\) 之后 \(e_2\) 会变成割边。因为这说明了它们初始在同一个环中,并且没有环只包含 \(e_1\) 或者 \(e_2\)
所以枚举删去哪条非割边,然后跑 \(\tt tarjan\),时间复杂度 \(O(m^2)\)
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 2005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,tot=1,f[M],cut[M],zxy[M];
int Ind,cnt,res,ban,ans,dfn[M],low[M];
struct edge{int v,next;}e[M<<1];
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++Ind;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if((i>>1)==ban || v==fa) continue;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]) cut[i>>1]=1,res++;
}
else low[u]=min(low[u],dfn[v]);
}
}
signed main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
e[++tot]=edge{v,f[u]},f[u]=tot;
e[++tot]=edge{u,f[v]},f[v]=tot;
}
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i,0);
for(int i=1;i<=m;i++) zxy[i]=cut[i];
cnt=res;
for(int i=1;i<=m;i++) if(!zxy[i])
{
for(int j=1;j<=n;j++) dfn[j]=low[j]=0;
res=0;ban=i;Ind=0;
for(int j=1;j<=n;j++)
if(!dfn[j]) tarjan(j,0);
ans=gcd(ans,res-cnt+1);
}
for(int i=1;i<=m;i++)
if(ans%i==0) printf("%d ",i);
puts("");
}