[POJ3417]Network/闇の連鎖(树上差分)

题目描述
传说中的“闇の連鎖”被人们称为 D a r k Dark Dark D a r k Dark Dark 是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它。经过研究,你发现 D a r k Dark Dark呈现无向图的结构,图中有 N N N 个节点和两类边,一类边被称为主要边,而另一类被称为附加边。 D a r k Dark Dark N – 1 N – 1 N1条主要边,并且 D a r k Dark Dark 的任意两个节点之间都存在一条只由主要边构成的路径。另外, D a r k Dark Dark 还有 M M M条附加边。你的任务是把 D a r k Dark Dark 斩为不连通的两部分。一开始 D a r k Dark Dark的附加边都处于无敌状态,你只能选择一条主要边切断。一旦你切断了一条主要边, D a r k Dark Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。但是你的能力只能再切断 D a r k Dark Dark 的一条附加边。现在你想要知道,一共有多少种方案可以击败 D a r k Dark Dark。注意,就算你第一步切断主要边之后就已经把 D a r k Dark Dark 斩为两截,你也需要切断一条附加边才算击败了 D a r k Dark Dark ( N ≤ 1 0 5 , M ≤ 2 ∗ 1 0 5 ) (N\leq 10^5,M\leq 2*10^5) (N105,M2105)

传送门


首先分析题目所给的树结构,“主要边”构成了一棵树,“附加边”则是非树边,把一条非树边添加到数中则会形成一个环,那么我们先只把主要边加入图中形成一棵树。

然后分析题目所给条件,总共切两次,一次切主要边,一次切附加边。要求把树切成两个部分,也就是说把某一个子树和原树分离。

如果我们选择切断点 x x x,点 y y y之间的主要边,那么一定要切断 x , y x,y x,y之间所有的附加边,才能真正的切断 x , y x,y x,y

所以当一条附加边k连接点 x , y x,y x,y时,对于树上 x , y x,y x,y之间路径上的每一条主要边,如果我们要切某一条主要边,都要切掉k这一条边才能真正切断。 我们可以把他称为:“覆盖”。

由于只能切两次,所以我们对于主要边被覆盖的次数分情况讨论:
1、如果第一步把覆盖 0 0 0次的主要边切断,那么树就已经被切断了,所以第二步切任何的附加边都可以。
2、如果第一步把覆盖 1 1 1次的主要边切断,那么第二步只能切覆盖这条主要边的附加边才能把树切断。
3、如果第一步把覆盖超过 1 1 1次的主要边切断,那么第二次且那个都无法切断树。

上面三种把树切断的方案,可以通过加法原理得到方案数。

(加法原理:做一件事情,完成它有n类方式,第一类方式有M1种方法,第二类方式有M2种方法,……,第n类方式有Mn种方法,那么完成这件事情共有M1+M2+……+Mn种方法。)

那么如何知道当前的边被覆盖了多少次呢?我们发现每加入一条附加边(x,y)那么就会对树上x,y之间的所有边产生了影响,那么就对应了一个树上的区间加法问题,这样可以用树上差分解决。

具体如何实现呢?记住这个操作:
我们对于每一个点记录一个 v a l val val值,初始化为 0 0 0,每当加入一条附加边 ( x , y ) (x,y) (x,y)那么就 v a l [ x ] + + , v a l [ y ] + + , v a l [ L C A ( x , y ) ] − = 2 val[x]++,val[y]++,val[LCA(x,y)]-=2 val[x]++,val[y]++,val[LCA(x,y)]=2,这样的话,在我们做好 v a l val val的加减之后对于整棵树 d f s dfs dfs一次,求出对于一个点 x x x的子树的 v a l val val值和 s u m sum sum s u m sum sum就是 x x x x x x的父节点 f a [ x ] fa[x] fa[x]之间这条主要边的被覆盖次数。

如图,蓝色的虚线边是附加边:
在这里插入图片描述

为什么这样子可以?因为之前提到一条一条附加边 ( x , y ) (x,y) (x,y)那么就会对树上 x , y x,y x,y之间的所有边产生了影响,如下图,黄色的边就是 ( x , y ) (x,y) (x,y)这条附加边影响到的主要边,黄色的边组成的路径是一定会经过 L C A LCA LCA的,但是一定不会经过 L C A LCA LCA上面的点,也就是说。 ( x , y ) (x,y) (x,y)只会影响到 L C A LCA LCA x x x,以及 L C A LCA LCA y y y的边,不会影响别的边。所以我们如果有一个在 L C A LCA LCA上面的点统计下来,为了不被影响,我们把 v a l [ L C A ] val[LCA] val[LCA]的值 − 2 -2 2
在这里插入图片描述

超超超超详细了吧,全网最详细题解了吧呜呜呜。


#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e5+10;
struct edge
{
	int x,y,c,next;
}a[N*2]; int len,last[N];
int dep[N],f[N][20];
void ins(int x,int y)
{
	a[++len].x=x;a[len].y=y;
	a[len].next=last[x];last[x]=len;
}
int bin[20];
void dfs(int x,int fa)//NlogN
{
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for(int i=1;bin[i]<=dep[x];i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for(int k=last[x];k;k=a[k].next)
	{
		int y=a[k].y;
		if(y==fa) continue;
		dfs(y,x);
	}
}
int LCA(int x,int y)//logN
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20;i>=0;i--) //跳到一个深度 
		if(dep[x]-dep[y]>=bin[i])
			x=f[x][i];
	if(x==y) return x;
	for(int i=20;i>=0;i--) //x,y一齐跳到LCA的孩子节点 
		if(dep[x]>=(1<<i) && f[x][i]!=f[y][i]) //如果f[x][i]=f[y][i]说明如果当前跳i会跳到LCA上面去  
			x=f[x][i],y=f[y][i];
	return f[x][0];
}	

int val[N];
int n,m;
void dfs2(int x,int fa)
{
	for(int k=last[x];k;k=a[k].next)
	{
		int y=a[k].y;
		if(y==fa) continue;
		dfs2(y,x);
		val[x]+=val[y];	
	}
}
int main()
{
	bin[0]=1;
	for(int i=1;i<=20;i++) bin[i]=bin[i-1]*2;
	scanf("%d%d",&n,&m);
	len=0; memset(last,0,sizeof(last)); 
	for(int i=1;i<n;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y); ins(y,x);
	}
	dfs(1,0);
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		int lca=LCA(x,y);
		val[x]++,val[y]++,val[lca]-=2;
	}
	dfs2(1,0);
	int ans=0;
	for(int i=2;i<=n;i++)
	{//加法原理 
		if(val[i]==0) ans+=m; //切断覆盖0次的主边,第二次可以且任意附加边
		else if(val[i]==1) ans+=1; //切断覆盖1次的主边,第二次只能切覆盖他的附加边
		//切覆盖次数>1次的主边无解	
	}
	printf("%d\n",ans);
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值