poj3417 Network

题目传送门

给你一颗树,还有一些别的边,先切除一条树边,然后切除一条非树边,有多少种方法可以切成两个不连通的部分

首先对着这棵树,加上一条边后一定会出现环,比如对于某棵树的一部分

x 表示非树边,我们发现,在这个环上任意一条树边切除之后显然只能切除那条非树边,而且如果我们再加上别的边显然切除这个环上的一边之后还是只能切除非树边,很容易联想到对于每个向这样的连接 x y 两点的环,这个环上每一条边切除之后都只能切除那条非树边,也就是如果切它就只有一种方案

于是我们统计这样的环上边的总数,或者我们可以换一种理解,每一个非树边都覆盖这个环上的每一边一次,对于覆盖 0次 的点切断之后有 m 种方案,覆盖 1次 有 1 种方案,其他的覆盖次数都无解,所以问题就转换成了统计每一条边的覆盖次数

我们发现暴力统计会 TLE ,其实对于这种统计覆盖的问题有一个专门的算法,叫

树上差分

其实我们就是在找树上两点之间的最短路,也就是两点距离的路径,树上两点距离容易联想到 LCA

用 w[x] 表示 x 与它的父亲节点连接的边的覆盖次数(权值) ,当我们要将 x y 两点的简单路径覆盖一次,我们你可以使

w[x]++, w[y]++;
w[lca(x, y)]-=2;

最后来一次 dfs ,类似差分的感觉

代码

注意一点就是对于根节点 r ,w[r]=0 ,但是实际上它并没有父亲节点,所以最后答案需要减掉 m

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rp(i, e) for(int i=1;i<=(e);i++)
#define pr(i, e) for(int i=(e);i>=1;i--)
#define rp0(i, e) for(int i=0;i<(e);i++)
#define pr0(i, e) for(int i=(e-1);i>=0;i--)
#define rps(i, b, e) for(int i=(b);i<=(e);i++)
#define prs(i, e, b) for(int i=(e);i>=(b);i--)
#define rpg(i, x) for(int i=head[x];i;i=e[i].nxt)
#define opf(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
using namespace std;
const int NR=1e5+10, MR=2e5+10, CFR=20;
struct edge
{
	int to, nxt;
}e[MR];
int n, m, head[NR], cnt, dep[NR], f[NR][CFR+5], w[NR], ans;
inline void add(int u, int v){e[++cnt]=(edge){v, head[u]};head[u]=cnt;}
void pre(int x, int fa)
{
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	rp(i, CFR)
		f[x][i]=f[f[x][i-1]][i-1];
	rpg(i, x)
		if(e[i].to!=fa)
			pre(e[i].to, x);
}
inline int lca(int x, int y)
{
	if(dep[x]<dep[y])swap(x, y);
	prs(i, CFR, 0)
		if(dep[f[x][i]]>=dep[y])
			x=f[x][i];
	if(x==y)return x;
	prs(i, CFR, 0)
		if(f[x][i]!=f[y][i])
			x=f[x][i], y=f[y][i];
	return f[x][0];
}
void dfs(int x, int fa)
{
	rpg(i, x)
		if(e[i].to!=fa)
		{
			dfs(e[i].to, x);
			w[x]+=w[e[i].to];
		}
}
int main()
{
	scanf("%d%d", &n, &m);
	int u, v;
	rp(i, n-1)
	{
		scanf("%d%d", &u, &v);
		add(u, v), add(v, u);
	}
	pre(1, 0);
	rp(i, m)
	{
		scanf("%d%d", &u, &v);
		w[u]++, w[v]++;
		w[lca(u, v)]-=2;
	}
	dfs(1, 0);
	rp(i, n)ans+=(!w[i])*m+(w[i]==1);
	printf("%d", ans-m);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值