#POJ 3417 Network (LCA + 树上差分)

Network

Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 8751 Accepted: 2499

Description

Yixght is a manager of the company called SzqNetwork(SN). Now she's very worried because she has just received a bad news which denotes that DxtNetwork(DN), the SN's business rival, intents to attack the network of SN. More unfortunately, the original network of SN is so weak that we can just treat it as a tree. Formally, there are N nodes in SN's network, N-1 bidirectional channels to connect the nodes, and there always exists a route from any node to another. In order to protect the network from the attack, Yixght builds M new bidirectional channels between some of the nodes.

As the DN's best hacker, you can exactly destory two channels, one in the original network and the other among the M new channels. Now your higher-up wants to know how many ways you can divide the network of SN into at least two parts.

Input

The first line of the input file contains two integers: N (1 ≤ N ≤ 100 000), M (1 ≤ M ≤ 100 000) — the number of the nodes and the number of the new channels.

Following N-1 lines represent the channels in the original network of SN, each pair (a,b) denote that there is a channel between node a and node b.

Following M lines represent the new channels in the network, each pair (a,b) denote that a new channel between node a and node b is added to the network of SN.

Output

Output a single integer — the number of ways to divide the network into at least two parts.

Sample Input

4 1
1 2
2 3
1 4
3 4

Sample Output

3

题目大意 : 输入一颗树, 包含N个点,在树上添加M条边,你要删除两条边, 规定一条是原来的树上的边, 另一条是新加的边,输出可以使原图不连通的方案数

思路 :  注意这是一棵树, 相邻两点之间只有一条边, 任意两点之间也只有一条边可达。当你添加一条边后, 无论添加在哪,一定会形成一个环, 也就意味着环上任意两点之间都有两条路径可达, 我们可以把这个图转换一下 :

右图的连通关系和左边是一样的 (任意两点之间均有两条路径可达)

记住这张图, 以下全是按照右图描述的,我们先看看每相邻两点之间的边数, 如果两个点之间只有一条边,那么方案数应该会比之前多 M个, 一条边意味着这是你原来的边, 把它割开一定会使图不连通, 而新加边无论切割哪一个都可以。如果两个点之间只有两条边, 说明一定一个来自原边,一个是新加的边, 那使图不连通只有一种方法, 把这两条边全部割了,方案数 + 1, 大于 2 的话是无法实现图的不连通的。 这样一来, 我们就可以用树上差分来做了,先把这个图想象成最基本的树, 根节点在最上面,下面全是他的孩子, 每次新加的边都可以如同上图一样转换,也就是看每条边的路径覆盖数, 一共N个点, N - 1条边(新加的边全部增加到原边的覆盖数上了)每条边的覆盖数就等于两个点之间深度较大的那个点的覆盖数。题目没有明确规定,可以默认1为根节点,最后直接从2 遍历到N, 按照刚才说的方案数更新方法,就可以把答案算出来了。

这道题必须得用链式前向星做, 邻接表会TLE

Accepted code

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;

#define sc scanf
#define ls rt << 1
#define rs ls | 1
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define MEM(x, b) memset(x, b, sizeof(x))
#define lowbit(x) ((x) & (-x))
#define P2(x) ((x) * (x))

typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 10;
const int INF = 0x3f3f3f3f;
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % MOD; b >>= 1; t = (t*t) % MOD; }return r; }

struct Edge
{
	int v, next;
}e[MAXN << 1];
int head[MAXN], cnt;
int dep[MAXN], p[MAXN][25], n, k; // p【i】【j】表示在i点上面2的j次方的祖先, dep为深度
int c[MAXN], dp[MAXN]; // c为树上差分数组, dp为每条边的覆盖数
bool vis[MAXN];
void init() {
	MEM(head, -1); cnt = 0;
}
void add(int from, int to) {  // 存边
	e[++cnt].v = to;
	e[cnt].next = head[from];
	head[from] = cnt;
}
void dfs(int x, int fa) { // 把所有点的深度,p数组填满
	dep[x] = dep[fa] + 1;
	p[x][0] = fa;
	for (int i = 1; i <= 21; i++)
		p[x][i] = p[p[x][i - 1]][i - 1];
	for (int i = head[x]; i != -1; i = e[i].next) {
		int vi = e[i].v;
		if (vi != fa) dfs(vi, x);
	}
}
int LCA(int x, int y) { // 最近共同祖先
	if (dep[x] > dep[y]) swap(x, y);
	for (int i = 21; i >= 0; i--) {
		if (dep[y] - (1 << i) >= dep[x])
			y = p[y][i];
	}
	if (x == y) return x;
	for (int i = 21; i >= 0; i--) {
		if (p[x][i] == p[y][i]) continue;
		x = p[x][i], y = p[y][i];
	}
	return p[x][0];
}
int DFS(int x) {  // 从根到各个点,递归更新覆盖数
	dp[x] = c[x]; vis[x] = 1;
	for (int i = head[x]; i != -1; i= e[i].next) {
		int vi = e[i].v;
		if (vis[vi]) continue;
		dp[x] += DFS(vi);
	}
	return dp[x];
}

int main()
{
	cin >> n >> k; init();
	for (int i = 1; i < n; i++) {
		int ui, vi;
		sc("%d %d", &ui, &vi);
		add(ui, vi); add(vi, ui);
	}
	dfs(1, 0);
	for (int i = 0; i < k; i++) {
		int ui, vi;
		sc("%d %d", &ui, &vi);
		int ans = LCA(ui, vi);
		c[ui]++, c[vi]++, c[ans] -= 2;  // 差分
	}
	DFS(1);
	int tot = 0;
	for (int i = 2; i <= n; i++) { // 根不需要判定,因为只有N - 1条边
		if (dp[i] == 0) tot += k;
		else if (dp[i] == 1) tot++;
	}
	printf("%d\n", tot);
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值