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

传送门


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

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

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

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

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

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

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

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

具体如何实现呢?记住这个操作:
我们对于每一个点记录一个valval值,初始化为00,每当加入一条附加边(x,y)(x,y)那么就val[x]++,val[y]++,val[LCA(x,y)]=2val[x]++,val[y]++,val[LCA(x,y)]-=2,这样的话,在我们做好valval的加减之后对于整棵树dfsdfs一次,求出对于一个点xx的子树的valval值和sumsumsumsum就是xxxx的父节点fa[x]fa[x]之间这条主要边的被覆盖次数。

如图,蓝色的虚线边是附加边:
[POJ3417]Network/闇の連鎖(树上差分)

为什么这样子可以?因为之前提到一条一条附加边(x,y)(x,y)那么就会对树上x,yx,y之间的所有边产生了影响,如下图,黄色的边就是(x,y)(x,y)这条附加边影响到的主要边,黄色的边组成的路径是一定会经过LCALCA的,但是一定不会经过LCALCA上面的点,也就是说。 (x,y)(x,y)只会影响到LCALCAxx,以及LCALCAyy的边,不会影响别的边。所以我们如果有一个在LCALCA上面的点统计下来,为了不被影响,我们把val[LCA]val[LCA]的值2-2
[POJ3417]Network/闇の連鎖(树上差分)

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


#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;
}

相关文章: