POJ3417 Network - 树链剖分+树状数组 / 树上差分

Network

Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 8651   Accepted: 2472

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 ( 1 N 1 0 5 ) N(1≤N≤10^5) 个节点构成的树,再添加 M ( 1 M 1 0 5 ) M(1≤M≤10^5) 条边。现需要删除原本树中的边和后来新添加的边各一条,问有多少种方法可以使得图不连通。

 
 

分析:

考虑添加一条边 e = ( u , v ) e'=(u,v) 后,图发生的变化:由于树上任意两个节点间路径唯一,因此添加 e e' 后产生了一个连接路径 ( u , v ) (u,v) 的环。所以可以得出以下结论:

  1. 对于不处在环上的原边,仅需删除这一条边就可以使图不连通,因此再删除任意一条新边即可
  2. 对于处在单个环上的原边,由于删除环上的任意一条边,图仍是连通图,因此要想在删除该边后使该图不连通,删除的新边只有 e e' 一种选择
  3. 对于处在超过一个环的原边,删除这条边后,无论删除哪一条都不会破坏图的连通性

因此我们只需计算出原本树上的每一条边,在添加 M M 条边后参与构成的环的个数即可。在添加 e = ( u , v ) e'=(u,v) 后,处于路径 u , v u,v 之间的树上的边参与构成的环的数量都增加了 1 1 . 因此对于每一条添加的边,只需让这条边连接的路径上的所有边的边权 + 1 +1 即可,而无需将这条边真的添加到树中,最后树上的每一条边的边权就是这条边参与构成的环的数量。

可是如果简单地搜索路径并修改路径上的每一条边的边权,时间复杂度是 O ( N M ) O(NM) ,无法在时间限制内解决问题。因此需要考虑如何高效地对树上一条路径上所有边的边权进行修改。首先为了方便,可以把每一条边拆为一个节点与两条边,这样就可以只需维护点的权值了。接下来可以在 O ( N ) O(N) 的时间复杂度内对树进行树链剖分,再使用线段树或树状数组维护区间上的节点的权值。由于每一条路径由不超过 O ( log 2 N ) O(\log_2N) 条链构成,而线段树或树状数组修改一次区间权值的时间复杂度是 O ( log 2 N ) O(\log_2N) ,因此总时间复杂度为 O ( N + M log 2 2 N ) O(N+M·\log_2^2N) .

 
 

下面贴代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int MAX_N = 200005;

struct Edge
{
	int to;
	int next;
	Edge() {}
	Edge(const int& to, const int& next)
		:to(to), next(next) {}
}G[2 * MAX_N];
int head[MAX_N];
int deg;
int root;

int cnt;
int par[MAX_N];
int depth[MAX_N];
int scnt[MAX_N];
int rk[MAX_N];
int top[MAX_N];
int id[MAX_N];
int son[MAX_N];

void add_edge(const int& u, const int& v);

void dfs1(const int v);
void dfs2(const int v, const int tp);

void init_t(const int n);

int bit[MAX_N], _n;
void add(int i, const int& x);
int sum(int i);

void update(int x, int y);


int main()
{
	int N, M, a, b;
	scanf("%d%d", &N, &M);

	init_t(2 * N);
	for (int i = 0; i < N - 1; ++i)
	{
		scanf("%d%d", &a, &b);
		a -= 2; b -= 2;
		add_edge(N + a, i);
		add_edge(i, N + b);
	}
	_n = 2 * N + 2;

	dfs1(root);
	dfs2(root, root);
	for (int i = 0; i < M; ++i)
	{
		scanf("%d%d", &a, &b);
		update(N + a - 2, N + b - 2);
	}

	ll ans = 0;
	for (int i = 0; i < N - 1; ++i)
	{
		int val = sum(id[i]);
		if (val == 0)
		{
			ans += M;
		}
		else if (val == 1)
		{
			++ans;
		}
	}
	printf("%lld\n", ans);

	return 0;
}

void add_edge(const int& u, const int& v)
{
	G[deg] = Edge(v, head[u]);
	head[u] = deg++;
	G[deg] = Edge(u, head[v]);
	head[v] = deg++;
}

void dfs1(const int v)
{
	scnt[v] = 1;
	depth[v] = depth[par[v]] + 1;
	int maxson = -1;
	for (int i = head[v]; ~i; i = G[i].next)
	{
		const int& to = G[i].to;
		if (to != par[v])
		{
			par[to] = v;
			dfs1(to);
			scnt[v] += scnt[to];
			if (scnt[to] > maxson)
			{
				maxson = scnt[to];
				son[v] = to;
			}
		}
	}
}

void dfs2(const int v, const int tp)
{
	top[v] = tp;
	id[v] = cnt;
	rk[cnt++] = v;

	if (~son[v])
	{
		dfs2(son[v], tp);
		for (int i = head[v]; ~i; i = G[i].next)
		{
			const int& to = G[i].to;
			if (to != par[v] && to != son[v])
			{
				dfs2(to, to);
			}
		}
	}
}


void init_t(const int n)
{
	deg = cnt;
	par[root] = root;
	fill(son, son + n + 1, -1);
	fill(head, head + n + 1, -1);
}

void add(int i, const int& x)
{
	++i;
	while (i <= _n)
	{
		bit[i] += x;
		i += i & -i;
	}
}

int sum(int i)
{
	++i;
	int s = 0;
	while (i)
	{
		s += bit[i];
		i -= i & -i;
	}
	return s;
}

void update(int x, int y)
{
	while (top[x] != top[y])
	{
		if (depth[top[x]] < depth[top[y]])
		{
			swap(x, y);
		}
		add(id[top[x]], 1);
		add(id[x] + 1, -1);
		x = par[top[x]];
	}
	if (id[x] < id[y])
	{
		swap(x, y);
	}
	add(id[y], 1);
	add(id[x] + 1, -1);
}

 
 

另外,由于我们不需要动态地查询每一条边的权值,只需在最后获取每一条边的边权,因此无需使用树状数组动态地维护边权,只需使用差分数组,并每次对差分数组的首尾两端进行修改,最后遍历一次差分数组即可得到每一条边的最终边权,这样做的时间复杂度可以降低为 O ( N + M log 2 N ) O(N+M·\log_2N) .

 
 

考虑到差分的思想,此题还可以采用树上差分的算法,在每一次修改时,在 O ( log 2 N ) O(\log_2N) 的时间复杂度内查询 L C A LCA ,并在 O ( 1 ) O(1) 的时间复杂度内修改差分数组。可以在 O ( N log 2 N ) O(N·\log_2N) 的时间复杂度内预处理出倍增数组,也可以在 O ( N ) O(N) 的时间复杂度内进行树链剖分,进而实现高效的 L C A LCA 查询。在使用前者时的时间复杂度是 O ( ( N + M ) log 2 N ) O((N+M)·\log_2N) .

 
 

下面贴代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

typedef long long ll;
const int MAX_N = 200005;
const int MAX_H = 19;


struct Edge
{
	int to;
	int next;
	Edge() {}
	Edge(const int& to, const int& next)
		:to(to), next(next) {}
}G[2 * MAX_N];
int head[MAX_N];
int deg;
int root;

int par[MAX_H][MAX_N];
int depth[MAX_N];

int dx[MAX_N];

int N, M;

void init_t(const int n);
void add_edge(const int& u, const int& v);
void dfs0(const int v, const int tp, int d);

void init(const int n);
int lca(int u, int v);

void update(int x, int y);
int dfs(ll& ans, const int v, const int tp);

int main()
{
	int a, b;
	scanf("%d%d", &N, &M);

	init_t(2 * N);
	for (int i = 0; i < N - 1; ++i)
	{
		scanf("%d%d", &a, &b);
		a -= 2; b -= 2;
		add_edge(N + a, i);
		add_edge(i, N + b);
	}
	
	init(2 * N - 1);
	for (int i = 0; i < M; ++i)
	{
		scanf("%d%d", &a, &b);
		update(N + a - 2, N + b - 2);
	}

	ll ans = 0;
	dfs(ans, root, -1);
	printf("%lld\n", ans);

	return 0;
}

void init_t(const int n)
{
	deg = 0;
	fill(head, head + n + 1, -1);
}

void add_edge(const int& u, const int& v)
{
	G[deg] = Edge(v, head[u]);
	head[u] = deg++;
	G[deg] = Edge(u, head[v]);
	head[v] = deg++;
}

void dfs0(const int v, const int tp, int d)
{
	par[0][v] = tp;
	depth[v] = d++;
	for (int i = head[v]; ~i; i = G[i].next)
	{
		const int& to = G[i].to;
		if (to != tp)
		{
			dfs0(to, v, d);
		}
	}
}

void init(const int n)
{
	dfs0(root, -1, 0);

	for (int k = 0; k + 1 < MAX_H; ++k)
	{
		for (int v = 0; v < n; ++v)
		{
			if (par[k][v] == -1)
			{
				par[k + 1][v] = -1;
			}
			else
			{
				par[k + 1][v] = par[k][par[k][v]];
			}
		}
	}
}

int lca(int u, int v)
{
	if (depth[u] > depth[v])
	{
		swap(u, v);
	}
	for (int k = 0; k < MAX_H; ++k)
	{
		if ((depth[v] - depth[u]) >> k & 1)
		{
			v = par[k][v];
		}
	}
	if (u == v)
	{
		return u;
	}
	for (int k = MAX_H - 1; k >= 0; --k)
	{
		if (par[k][u] != par[k][v])
		{
			u = par[k][u];
			v = par[k][v];
		}
	}
	return par[0][u];
}

void update(int x, int y)
{
	int a = lca(x, y);

	++dx[x];
	++dx[y];
	--dx[a];

	if (~par[0][a])
	{
		--dx[par[0][a]];
	}
}

int dfs(ll& ans, const int v, const int tp)
{
	int d = dx[v];
	for (int i = head[v]; ~i; i = G[i].next)
	{
		const int& to = G[i].to;
		if (to != tp)
		{
			d += dfs(ans, to, v);
		}
	}

	if (d < 2 && v < N - 1)
	{
		if (d == 0)
		{
			ans += M;
		}
		else if (d == 1)
		{
			++ans;
		}
	}
	return d;
}

原创文章 42 获赞 22 访问量 3037

猜你喜欢

转载自blog.csdn.net/weixin_44327262/article/details/98478952