Tree(树形dp,树分治入门)

https://cn.vjudge.net/problem/POJ-1741

Give a tree with n vertices,each edge has a length(positive integer less than 1001).
Define dist(u,v)=The min distance between node u and v. 
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k. 
Write a program that will count how many pairs which are valid for a given tree. 

Input

The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l. 
The last test case is followed by two zeros. 

Output

For each test case output the answer on a single line.

Sample Input

5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0

Sample Output

8

题意:给定一棵N个节点、边上带权的树,再给出一个K,询问有多少个数对(i,j)满足i<j,且i与j两点在树上的距离小于等于K。

对于一棵有根树, 树中满足要求的一个数对所对应的一条路径,必然是以下两种情况之一:

1、经过根节点

2、不经过根节点,也就是说在根节点的一棵子树中

对于情况2,可以递归求解,下面主要来考虑情况1。



设点i的深度为Depth[i],父亲为Parent[i]。

若i为根,则Belong[i]=-1,若Parent[i]为根,则Belong[i]=i,否则Belong[i]=Belong[Parent[i]]。


这三个量都可以通过一次BFS求得。

我们的目标是要统计:有多少对(i,j)满足i<j,Depth[i]+Depth[j]<=K且Belong[i]<>Belong[j]


如果这样考虑问题会变得比较麻烦,我们可以考虑换一种角度:

设X为满足i<j且Depth[i]+Depth[j]<=K的数对(i,j)的个数


设Y为满足i<j,Depth[i]+Depth[j]<=K且Belong[i]=Belong[j]数对(i,j)的个数


那么我们要统计的量便等于X-Y

求X、Y的过程均可以转化为以下问题:

已知A[1],A[2],...A[m],求满足i<j且A[i]+A[j]<=K的数对(i,j)的个数

对于这个问题,我们先将A从小到大排序。

设B[i]表示满足A[i]+A[p]<=K的最大的p(若不存在则为0)。我们的任务便转化为求出A所对应的B数组。那么,若B[i]>i,那么i对答案的贡献为B[i]-i。

显然,随着i的增大,B[i]的值是不会增大的。利用这个性质,我们可以在线性的时间内求出B数组,从而得到答案。

综上,设递归最大层数为L,因为每一层的时间复杂度均为“瓶颈”——排序的时间复杂度O(NlogN),所以总的时间复杂度为O(L*NlogN)

然而,如果遇到极端情况——这棵树是一根链,那么随意分割势必会导致层数达到O(N)级别,对于N=10000的数据是无法承受的。因此,我们在每一棵子树中选择“最优”的点分割。所谓“最优”,是指删除这个点后最大的子树尽量小。这个点可以通过树形DP在O(N)时间内求出,不会增加时间复杂度。这样一来,即使是遇到一根链的情况时,L的值也仅仅是O(logN)的。

因此,改进后算法时间复杂度为O(Nlog^2N),可以AC。

大佬的博客https://www.cnblogs.com/ECJTUACM-873284962/p/6618855.html

 

没想到用vector存边会T,嘤嘤嘤,以后还是用链式前向星吧……

入门题就这么难理解,深搜越想越乱,自闭

//#include<bits/stdc++.h> 
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e4+5; 
int n,k;
struct node{
	int v,w,next;
}e[N<<1];
int t,head[N];
bool vis[N];
int num[N]; 
int zx[N];//重心 
int deep[N];//路径长度//deep[0]子节点个数 
int dis[N]; 
int ans,allnode,root;
void init(int n){
	t=0;
	for(int i=1;i<=n;i++) {
		head[i]=-1;
		vis[i]=false;
	}
	root=0;
	ans=0;
	allnode=n;
	zx[0]=INF;
}
void add(int u,int v,int w){
	e[t].v=v;
	e[t].w=w;
	e[t].next=head[u];
	head[u]=t++;
}
void getroot(int u,int fa){//获取重心,以重心为根 
	num[u]=1;
	zx[u]=0;
	for(int i=head[u];~i;i=e[i].next){
		node &it=e[i];
		int &v=it.v;
		if(v==fa||vis[v]) continue;
		getroot(v,u);
		num[u]+=num[v];
		zx[u]=max(zx[u],num[v]);
	} 
	zx[u]=max(allnode-num[u],zx[u]);
	if(zx[u]<zx[root]) root=u;
} 
void getdeep(int u,int fa){//获取子树所有节点与根的距离 
	deep[++deep[0]]=dis[u];
	for(int i=head[u];~i;i=e[i].next){
		node &it=e[i];
		int &v=it.v;
		int &w=it.w;
		if(v==fa||vis[v]) continue;
		dis[v]=dis[u]+w;
		getdeep(v,u);
	}
}
int cal(int x,int now){//计算当前以重心x的子树下,所有情况的答案 
	dis[x]=now;
	deep[0]=0;
	getdeep(x,0);
	sort(deep+1,deep+1+deep[0]);
	int cnt=0;
	int i=1,j=deep[0];
	while(i<j){
		if(deep[i]+deep[j]<=k){
			cnt+=j-i;
			i++;
		}
		else j--;
	}
	return cnt;
}
void solve(int u){
	vis[u]=1;
	ans+=cal(u,0);
	for(int i=head[u];~i;i=e[i].next){
		node &it=e[i];
		int &v=it.v;
		int &w=it.w;
		if(vis[v]) continue;
		ans-=cal(v,w);
		allnode=num[v];
		root=0;
		getroot(v,u);
		solve(root);
	} 
}
int main(){
    while(~scanf("%d%d",&n,&k)&&n&&k){
    	init(n);
    	for(int i=1;i<n;i++){
    		int u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
    		add(u,v,w);
    		add(v,u,w);
		}
		getroot(1,0);
		solve(root);
		printf("%d\n",ans);	
	}
	return 0;
}


 

上一篇:牛客提高D4t3 清新题


下一篇:普通莫队--洛谷P1997 【faebdc的烦恼】