CodeForces - 1060E Sergey and Subway(树上任意两点之间的距离和)

Sergey and Subway

Sergey Semyonovich is a mayor of a county city N and he used to spend his days and nights in thoughts of further improvements of Nkers' lives. Unfortunately for him, anything and everything has been done already, and there are no more possible improvements he can think of during the day (he now prefers to sleep at night). However, his assistants have found a solution and they now draw an imaginary city on a paper sheet and suggest the mayor can propose its improvements.

Right now he has a map of some imaginary city with n subway stations. Some stations are directly connected with tunnels in such a way that the whole map is a tree (assistants were short on time and enthusiasm). It means that there exists exactly one simple path between each pair of station. We call a path simple if it uses each tunnel no more than once.
One of Sergey Semyonovich's favorite quality objectives is the sum of all pairwise distances between every pair of stations. The distance between two stations is the minimum possible number of tunnels on a path between them.
Sergey Semyonovich decided to add new tunnels to the subway map. In particular, he connected any two stations u and v that were not connected with a direct tunnel but share a common neighbor, i.e. there exists such a station w that the original map has a tunnel between u and w and a tunnel between w and v. You are given a task to compute the sum of pairwise distances between all pairs of stations in the new map.

Input
The first line of the input contains a single integer
n
(2≤n≤200000) — the number of subway stations in the imaginary city drawn by mayor’s assistants. Each of the following
n−1 lines contains two integers ui and vi (1≤ui,vi≤n, ui≠vi), meaning the station with these indices are connected with a direct tunnel.
It is guaranteed that these n stations and n−1 tunnels form a tree.
Output
Print one integer that is equal to the sum of distances between all pairs of stations after Sergey Semyonovich draws new tunnels between all pairs of stations that share a common neighbor in the original map.

Examples
input

4
1 2
1 3
1 4
output
6
input
4
1 2
2 3
3 4
output
7

题意:给出一个树,把树上任意两个相隔一个点的点加一条边,问加完边之后任意两点的距离和是多少.
思路:51nod上有这样一个题目,让求树上每个点到其他所有节点的距离和,打印出n个数,代表答案.
我们可以先一遍dfs求出每个节点到他的所有子节点的距离和,那么根节点存的就是他到树上每个节点的
距离和,这样根节点的儿子就可以由他转移过来,就可以求出题目要求的答案来.
这题也可以这样做,稍微不同的是往下dfs也好,往下转移也好,都是向孙子转移,父亲会由他的爷爷
dfs过来,往下转移的时候也得向孙子.这里的根必须是叶子节点,这样可以避免很多问题,而根节点的儿子
需要在单独向下转移,因为他跟根节点都是独立的,也可以看成根.
后面讲一个简单思路.

代码:

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 2e5+5;

struct node
{
	int u,v,ne;
} e[maxn<<1];

int n;
int head[maxn],len;
int ve[maxn],num[maxn];
ll d[maxn];
ll ans;

void add(int u,int v)
{
	e[len].u = u;
	e[len].v = v;
	e[len].ne = head[u];
	head[u] = len++;
}

void dfs(int x,int p)
{
	ve[x] = 1;
	for(int i = head[x];~i;i = e[i].ne)
	{
		int v = e[i].v;
		if(v == p) continue;
		
		dfs(v,x);
		
		for(int j = head[v];j!= -1;j = e[j].ne)
		{
			int sv = e[j].v;
			if(sv == x) continue;
			d[x]+= d[sv]+ve[sv];//distance to grandson
		}
		d[x]++;//distance to son
		ve[x]+= ve[v];
	}
	
	return ;
}

void dfs2(int x,int p)
{
	ans+= d[x];
	for(int i = head[x];~i;i = e[i].ne)
	{
		int v = e[i].v;
		if(v == p) continue;
		
		for(int j = head[v];j!= -1;j = e[j].ne)
		{
			int sv = e[j].v;
			if(sv == x) continue;
			
			d[sv]+= d[x]-d[sv]-ve[sv]+n-ve[sv]-(ve[v]-ve[sv]);
			dfs2(sv,v);
		}
		if(p == 0)// if x is root,the son is independent
		{
			d[v]++;
			dfs2(v,x);
		}
	}
	
	return ;
}

int main()
{
	mem(head,-1);
	cin>>n;
	for(int i = 1,x,y;i< n;i++)
	{
		scanf("%d %d",&x,&y);
		add(x,y);
		add(y,x);
		num[x]++;
		num[y]++;
	}
	
	int st;
	for(int i = 1;i<= n;i++)
	{
		if(num[i] == 1)
		{
			st = i;
			break;
		}
	}
	
	dfs(st,0);
	dfs2(st,0);
	cout<<ans/2<<endl;
	
	return 0;
}

如果是求树上任意两点之间的距离和,我们有一个比较简单的做法,就是不考虑点,考虑边.
很显然,每条边被使用的次数是固定的,即边的一端的节点数目乘以另一端的节点数目.这样我们就能求
树上任意两点之间的距离和了,这道题就是一个变形.我们想,假如一个节点在树上距离另一个节点偶数条边,
那么他是不是可以两个两个地跳过去,只需要走一半的路程就到了.假如有奇数条边,两个两个条,最后难免
剩一条边,其实就是(边数+1)/2.所以,假如我们给树规定一个层次的话,只有奇数层的节点和偶数层的节点
的路径会出现这种状况,其余的都能两步两步跳到.所以,我们只需要把这样的情况多+1,最后结果/2即可.

代码:

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 2e5+5;

struct node
{
	int u,v,ne;
} e[maxn<<1];

int n;
int head[maxn],len;
int ve[maxn],b[maxn];

void add(int u,int v)
{
	e[len].u = u;
	e[len].v = v;
	e[len].ne = head[u];
	head[u] = len++;
}

void dfs(int x,int p,int k)
{
	ve[x] = 1;
	b[x] = k;
	for(int i = head[x];~i;i = e[i].ne)
	{
		int v = e[i].v;
		if(v == p) continue;
		
		dfs(v,x,k^1);
		ve[x]+= ve[v];
	}
	return ;
}

int main()
{
	mem(head,-1);
	cin>>n;
	for(int i = 1,x,y;i< n;i++)
	{
		scanf("%d %d",&x,&y);
		add(x,y);
		add(y,x);
	}
	
	dfs(1,0,0);
	
	ll ans = 0,sum = 0;
	for(int i = 1;i<= n;i++)
	{
		ans+= 1ll*ve[i]*(n-ve[i]);
		sum+= b[i];
	}
	
	ans+= sum*(n-sum);
	
	cout<<ans/2<<endl;
	
	return 0;
}
引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值