把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

bzoj4238-Loj2881-JOISC2014 电压【树上差分】

题目链接

题目解析

如果对每一条边都尝试染色的话,复杂度显然不可过。

考虑从宏观上大局考虑。(这句话怎么这么别扭,是我语文太菜了

如果图中有奇环,那么奇环上的边必须被选,否则如果选了其它的边,奇环上的边就一定有一条左右端点颜色一样。

同样考虑偶环,我们发现偶环上的边必须不被选,否则如果选了偶环上的边,那么偶环上一定有一条边左右端点颜色一样。

总结一下,就是:
如果没有奇环,那么偶环以外的边可以随便选。

如果有奇环,那么能选的边就是所有奇环上的边的交集,并且不在偶环上。

不过,在偶环上的边一定不在奇环上,因为如果一条边既在偶环上,又在奇环上,那通过这条边相连的奇环和偶环可以组成一个更大的奇环(可以画图看一下),而这条边不在这个更大的奇环上。(当然,我的做法判不了这个更大的奇环,所以需要判偶环)

算了,还是画个图:(这个图可能要先看一下下面的具体做法再来看

(可以看到从树上下下来长这样,可以更直观地看到\(2-3\)边一定不在大奇环\(1-2-5-4-3-1\)

可以用\(dfs\)序解决这个问题,如果搜到一条返祖边就说明成环了,而我们可以通过\(Δdep\)判断是偶环还是奇环,然后树上差分就可以算出这条边在多少个奇环/偶环上。

最后统计答案找到所有奇环覆盖数\(==\)奇环总数,并且不在偶环上的边的总数。

注意一下,我们在算答案的时候是没有考虑返祖边的,因为如果有\(2\)个以上的奇环,那么这两条返祖边都不在一起,肯定不在交集里面。但是,如果只有一个奇环的话,这条返祖边是可行的,所以答案要\(+1\)

最后再注意一下程序写法,是差分边,不是差分点,注意一下细节。


►Code View

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define N 100005
#define M 200005
#define MOD 998244353
#define INF 0x3f3f3f3f
int rd()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
	return f*x;
}
struct node{
	int nxt,v;
}e[M<<1];
int hd[N],cnt=1;
void add(int u,int v)
{
	e[++cnt].nxt=hd[u],e[cnt].v=v;
	hd[u]=cnt;
}

int n,m;
bool vis[N],rt[N];
int dep[N],sum[N][2]/*差分数组 标记这个点头上的那条边 注意是边*/,tot/*奇环个数*/;
int f[N];//记录这个点头上的边的编号 
void dfs(int u)
{
	vis[u]=1;
	for(int i=hd[u];i;i=e[i].nxt)
	{
		int v=e[i].v;
		//if(v==fa) continue;
		if(i==f[u]||(i^1)==f[u]) continue;
		if(vis[v])//返祖边
		{
			if(dep[v]>dep[u]) continue;//走到了返祖边的反向边 
			int d=(dep[u]-dep[v])&1;
			sum[u][d]++,sum[v][d]--;
			if(!d) tot++;
		} 
		else
		{
			f[v]=i;
			dep[v]=dep[u]+1;
			dfs(v);
			sum[u][0]+=sum[v][0],sum[u][1]+=sum[v][1];//差分合并 
		}
	}
}
int main()
{
	n=rd(),m=rd();
	for(int i=1;i<=m;i++)
	{
		int u=rd(),v=rd();
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++)
		if(!vis[i]) 
		{
			rt[i]=1;
			dfs(i);
		}
	int ans=0;
	//printf("%d\n",tot);
	//for(int i=1;i<=n;i++)
	//	printf("%d\n",sum[i][1]);
	for(int i=1;i<=n;i++)
		if(sum[i][0]==tot&&!sum[i][1]&&!rt[i])//在所有奇环上 不在任何偶环上 并且不是根(根头顶上没有边
			ans++;
	if(tot==1) ans++;//只有一个奇环 那么那条返祖边也要算上 
	printf("%d\n",ans); 
	return 0;
}
/*
因为这道题是按边算 可能会有重边 所以只能写前向星 按边来
主要是v==fa那里的判断 不能判断是不是父节点 而要判是不是当初来的那条边或它的反向边 
样例2就可以帮助理解这种情况 
*/
posted @ 2020-12-01 16:16  Starlight_Glimmer  阅读(163)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end