纪念一下我逝去的语文(110的蒟蒻膜拜AK的大佬)
一、题目
传送门
题意:
对于一个图进行二染色,求删去哪些边可以是得染色成功(一定有解),输出可能的边数及可能边的编号(输入顺序)。
数据范围:
1
≤
n
≤
1
0
4
,
0
≤
m
≤
1
0
4
1\leq n\leq 10^{4},0\leq m\leq 10^{4}
1≤n≤104,0≤m≤104
二、解法
0x01 前置芝士
先给出一个结论:一个图能染色成功的充要条件是该图中不存在奇环(边数为奇数的环)。
怎么理解这个结论呢?我们先建一颗搜索树,我们把边慢慢加 ,新加入的边会让搜索树构成环,如果此环为奇环,那么树上路径的边数为偶数,两点的颜色一样,故不能有奇环,详细证明需要讨论链式和弯折式的情况。
0x02 分析
我们先建一颗生成树,把没有出现过得边加入。
首先如果没有奇环,直接输出所有边即可。
如果有一个奇环,我们删去环上任意一条边即可。
我们考虑多个奇环的情况,由于要同时破环所有奇环,我们考虑把每次加入边所对应的树上路径全部加1,最后统计所有权值等于奇环数的树边,树上差分可以做到
O
(
n
)
O(n)
O(n)。
当这里还存在一个难以解决的问题,形成偶环的边可能回和形成奇环的边组合成奇环,而我们并没有统计到,我们尝试对组合的情况进行讨论。
设奇环树上边数为J,偶环树上边数为O。
第一种情况(两边路径均有交集),我们发现第一条红线组成了一个奇环,第二条红线组成了一个偶环,
如果删去树上边的环,剩下的两个红边组成的环的数量为
O
−
J
+
2
O-J+2
O−J+2,易得改换为奇环,所以不能删去树上边。
第二种情况,若删去树边,则能组成的新环边数为
O
−
s
1
+
s
2
+
2
O-s_1+s_2+2
O−s1+s2+2,
s
1
,
s
2
s_1,s_2
s1,s2为奇环树上的两边边数,所以
s
1
,
s
2
s_1,s_2
s1,s2同奇同偶,易得该新环为奇环,故该树边不能选。
这两种情况是最基本的,可以衍生出其他情况,我们可以发现偶环的树上路径都是不能选的,这个也可以通过树上差分实现。
0x03 代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 10005;
int read()
{
int num=0,flag=1;
char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,m,k,cnt,tot,thm,flag,num[MAXN],c[MAXN],f[MAXN],p[MAXN],dep[MAXN],ans[MAXN],fa[MAXN][20];
bool vis[MAXN];
struct node
{
int u,v,id;
}a[MAXN],b[MAXN];
struct edge
{
int v,next,id;
}e[MAXN*2];
int findSet(int x)
{
if(p[x]^x) p[x]=findSet(p[x]);
return p[x];
}
void dfs(int u,int par)
{
fa[u][0]=par;
dep[u]=dep[par]+1;
for(int i=1;i<20;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=f[u];i;i=e[i].next)
if(e[i].v^par)
{
num[e[i].v]=e[i].id;
dfs(e[i].v,u);
}
}
int Lca(int u,int v)
{
for(int i=19;i>=0;i--)
{
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
if(dep[fa[v][i]]>=dep[u])
v=fa[v][i];
}
if(u==v) return u;
for(int i=19;i>=0;i--)
if(fa[u][i]^fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
void make(int u,int par)
{
vis[u]=1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==par) continue;
make(v,u);
c[u]+=c[v];
}
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
p[i]=i;
for(int i=1;i<=m;i++)
a[i]=node{read(),read(),i};
for(int i=1;i<=m;i++)
{
int x=findSet(a[i].u),y=findSet(a[i].v);
if(x^y)
{
p[x]=y;
e[++tot]=edge{a[i].u,f[a[i].v],a[i].id},f[a[i].v]=tot;
e[++tot]=edge{a[i].v,f[a[i].u],a[i].id},f[a[i].u]=tot;
}
else
b[++k]=a[i];
}
for(int i=1;i<=n;i++)
if(!dep[i]) dfs(i,0);
for(int i=1;i<=k;i++)
{
int u=b[i].u,v=b[i].v,lca=Lca(u,v);
int len=dep[u]+dep[v]-dep[lca]*2,delta=1;
if(len&1) delta=-1;
else flag++,thm=b[i].id;
c[u]+=delta,c[v]+=delta,c[lca]-=2*delta;
}
for(int i=1;i<=n;i++)
if(!vis[i]) make(i,0);
if(!flag)
for(int i=1;i<=m;i++)
ans[++cnt]=i;
else
for(int i=1;i<=n;i++)
if(c[i]==flag)
ans[++cnt]=num[i];
if(flag==1) ans[++cnt]=thm;
sort(ans+1,ans+1+cnt);
printf("%d\n",cnt);
for(int i=1;i<=cnt;i++)
printf("%d ",ans[i]);
}