暗的连锁(​POJ 3417​)

该博客讨论了一个无向图的割点问题,图由主要边和附加边组成。目标是找到切断主要边并随后切断一条附加边以将图分为两部分的方案数。输入包括节点数、主要边和附加边信息,输出是可行方案的数量。解决方案涉及深度优先搜索(DFS)来计算节点的祖先,并通过差分数组和路径覆盖计算可能的切割方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题来自:POJ 3417

Dark 是一张无向图,图中有 NN 个节点和两类边,一类边被称为主要边,而另一类被称为附加边。Dark 有 N - 1N−1 条主要边,并且 Dark 的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark 还有 MM 条附加边。

你的任务是把 Dark 斩为不连通的两部分。一开始 Dark 的附加边都处于无敌状态,你只能选择一条主要边切断。一旦你切断了一条主要边,Dark 就会进入防御模式,主要边会变为无敌的而附加边可以被切断。但是你的能力只能再切断 Dark 的一条附加边。

现在你想要知道,一共有多少种方案可以击败 Dark。注意,就算你第一步切断主要边之后就已经把 Dark 斩为两截,你也需要切断一条附加边才算击败了 Dark。

输入格式

第一行包含两个整数 NN 和 MM;

之后 N - 1N−1 行,每行包括两个整数 AA 和 BB,表示 AA 和 BB 之间有一条主要边;

之后 MM 行以同样的格式给出附加边。

输出格式

输出一个整数表示答案。

样例

InputcopyOutputcopy
4 1
1 2
2 3
1 4
3 4
3

数据范围与提示

对于 20\%20% 的数据,1\le N,M\le 1001≤N,M≤100;

对于 100\%100% 的数据,1\le N\le 10^5,1\le M\le 2\times 10^51≤N≤105,1≤M≤2×105。数据保证答案不超过 2^{31}-1231−1。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+4;
int f[N][30],depth[N],cnt;
int num[N],fx[N],fy[N],ff[N];
struct dd
{
	int nxt,to;
} e[N];
int h[N];
void add(int u,int v)
{
	e[++cnt].nxt=h[u];
	h[u]=cnt;
	e[cnt].to=v;
}
void dfs(int x)
{
	memset(depth,-1,sizeof(depth));
	depth[x]=1;// depth[x]记录x的深度 
	depth[0]=0;
	queue<int>q;
	q.push(x);
	while(!q.empty())
	{
		int t=q.front();
		q.pop();
		for(int i=h[t]; i; i=e[i].nxt)
		{
			int j=e[i].to;
			if(depth[j]==-1)
			{
				depth[j]=depth[t]+1;
				q.push(j);
				f[j][0]=t;       //2的0次方=1,即fy是j的父亲 
				for(int k=1; k<=19; k++)
					f[j][k]=f[f[j][k-1]][k-1];// j的k次方 == j的 k-1次方 + j的 k-1次方 
			}
		}

	}
}
int lca(int x,int y)
{
	if(depth[x]<depth[y]) swap(x,y);

	for(int i=20; i>=0; i--)
		if(depth[f[x][i]]>=depth[y]) 
			x=f[x][i];             //往上寻找lca 
	if(x==y) return x;
	for(int i=20; i>=0; i--)
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	return f[x][0];
}
void getf(int now,int fa)
{
	ff[now]+=num[now];    //ff[x]就是x与它的父节点之间的“树边”被附加边覆盖的次数
	for(int i=h[now]; i; i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		getf(v,now);
		ff[now]+=ff[v];
	}
}
int main()
{
	int n,m,ans=0,x,y;
	cin>>n>>m;
	for(int i=1; i<n; i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1);
	for(int i=1; i<=m; i++)
	{
		scanf("%d%d",&fx[i],&fy[i]);
		num[fx[i]]++;    //差分 
		num[fy[i]]++;
		num[lca(fx[i],fy[i])]-=2;
	}
	getf(1,0);    // 
	for(int i=2; i<=n; i++)
	{
		if(ff[i]==0) ans+=m;//第一次把覆盖零次的边切掉,第二次可以从 m条附着边中随意选一条进行切掉 
		if(ff[i]==1) ans++;//第一次把覆盖一次的边切掉,第二次只能切这一条附加边边 
	}
	cout<<ans;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值