CF1680F Lenient Vertex Cover题解

题意

给定一张 n n n 个节点, m m m 条边的无向连通图。求输出一组节点覆盖,使得最多有一条边的两端节点都被选择。

  • 2 ≤ n ≤ 1 0 6 2\le n\le 10^6 2n106
  • 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) n1mmin(106,2n×(n1))

思路

考虑所有边都只有一个端点被选的覆盖。如果存在这样一组覆盖,那么原图一定是二分图

接下来,考虑有一个边的两个端点都被选的情况。我们发现如果原图不是二分图,那么一定存在奇环。我们希望找到一条边,将这条边两个端点合并后,能使得原图变成二分图,就满足了题目。

所以这条边一定要存在于所有奇环上,且不能存在于任意偶环上(合并后要让奇环消失且不能产生新的奇环)。这样就想到在 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)

我的程序又臭又长(

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值