题意
给定一张 n n n 个节点, m m m 条边的无向连通图。求输出一组节点覆盖,使得最多有一条边的两端节点都被选择。
- 2 ≤ n ≤ 1 0 6 2\le n\le 10^6 2≤n≤106
- n − 1 ≤ m ≤ min ( 1 0 6 , n × ( n − 1 ) 2 ) n-1\le m\le\min(10^6,\frac{n\times(n-1)}2) n−1≤m≤min(106,2n×(n−1))
思路
考虑所有边都只有一个端点被选的覆盖。如果存在这样一组覆盖,那么原图一定是二分图。
接下来,考虑有一个边的两个端点都被选的情况。我们发现如果原图不是二分图,那么一定存在奇环。我们希望找到一条边,将这条边两个端点合并后,能使得原图变成二分图,就满足了题目。
所以这条边一定要存在于所有奇环上,且不能存在于任意偶环上(合并后要让奇环消失且不能产生新的奇环)。这样就想到在 dfs 进行二分图染色的同时统计所有的环,对每个边记录它存在的奇环和偶环数量。
统计时需要用到一个树上差分的小技巧。我们知道无向图 dfs 中除了树边就是回边,我们发现只要考虑由若干个树边和一个回边组成的环就可以了,其他的环一定是由这些环的边组成的。只要在发现环时,对回边记一次数,再对树边路径差分统计一下。最后总的 dfs 一次统计子树和,就能统计出每条边的奇环偶环个数。
然后我们就找到一条存在于所有奇环上的边,将其删除就能得到二分图。如果这条边不存在,输出 NO。最后进行一次二分图染色,注意要让被删边的端点染为 1。
实现时有一些细节要注意,例如要处理一下每个点在 dfs 上的父边。
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define F first
#define S second
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
inline int read(){//快读快写
int x=0,f=0;char ch=getchar();while(!isdigit(ch))f|=ch=='-',ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();return f?-x:x;
}
template<typename T>inline void write(T x){
if(x<0)x=-x,putchar('-');if(x>9)write(x/10);putchar(x%10^48);
}
int n,m,odd,cnt0[1000005],cnt1[1000005],c[1000005],fe[1000005];//cnt0偶环个数,cnt1奇环个数,c节点颜色,fe父边
bool vis[1000005];
pii e[1000005];
vector<pii> G[1000005];
void dfs(int u,int p,int cl)//二分图染色同时找环
{
c[u]=cl;
vis[u]=1;
fe[u]=p;
for(auto pp:G[u])
if (pp.S!=p)
{
int to=pp.F,id=pp.S;
if (c[to]==-1)
dfs(to,id,cl^1);
else if (c[to]==cl^1&&vis[to])//偶环
cnt0[id]++,cnt0[p]++,cnt0[fe[to]]--;
else if (c[to]==cl&&vis[to])//奇环
odd++,cnt1[id]++,cnt1[p]++,cnt1[fe[to]]--;
}
vis[u]=0;//回溯时撤销访问标记
}
void dfs2(int u)//整理差分数组,得到每条边的计数
{
vis[u]=1;
for(auto pp:G[u])
if (!vis[pp.F])
{
int to=pp.F;
dfs2(to);
if (fe[u]!=-1&&fe[to]!=-1);
cnt0[fe[u]]+=cnt0[fe[to]],cnt1[fe[u]]+=cnt1[fe[to]];
}
}
void dfs3(int u,int cl)//二分图染色
{
c[u]=cl;
for(auto pp:G[u])
if (c[pp.F]==-1)
{
int to=pp.F;
dfs3(to,cl^1);
}
}
void run()
{
odd=0;//一系列初始化
n=read(),m=read();
for(int i=0;i<n;i++)
G[i].clear();
fill(cnt0,cnt0+m+3,0);
fill(cnt1,cnt1+m+3,0);
fill(c,c+n+3,-1);
fill(vis,vis+n+3,0);
for(int i=0;i<m;i++)
{
e[i]=mp(read()-1,read()-1);
G[e[i].F].pb(mp(e[i].S,i));
G[e[i].S].pb(mp(e[i].F,i));
}
dfs(0,-1,0);
fill(vis,vis+n+3,0);
dfs2(0);
int x=-1,y=-1;
if (odd)//存在奇环
{
for(int i=0;i<m&&x==-1;i++)
if (cnt1[i]==odd&&cnt0[i]==0)
x=i;
if (x==-1)
{
puts("NO");
return;
}
y=e[x].F,x=e[x].S;
sort(G[x].begin(),G[x].end());
sort(G[y].begin(),G[y].end());
G[y].erase(lower_bound(G[y].begin(),G[y].end(),mp(x,-1)));//删边
G[x].erase(lower_bound(G[x].begin(),G[x].end(),mp(y,-1)));
}
fill(c,c+n+3,-1);
dfs3(0,1);
int f=(x==-1?0:c[x]^1);//保证被删边的端点颜色为1
puts("YES");
for(int i=0;i<n;i++)
write(c[i]^f);
putchar('\n');
}
int main()
{
int TT=read();
while(TT--)
run();
return 0;
}
- 时间复杂度: O ( n + m ) \operatorname O(n+m) O(n+m)
- 空间复杂度: O ( n + m ) \operatorname O(n+m) O(n+m)
我的程序又臭又长(