Hdu 5401 Persistent Link/cut Tree 树上分治/记忆化搜索

Persistent Link/cut Tree

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 535    Accepted Submission(s): 163


Problem Description
Teacher Mai has  m+1  trees,  T0,T1,,Tm T0  consists one vertex numbered  0 .

He generated the  Ti  in this way. Get a copy of  Tai  and  Tbi . Add an edge with length  li  between vertex numbered  ci  in  Tai  and  di  in  Tbi . Relabel the vertices in the new tree. Let  k  be the number of vertices in  Tai . He keeps labels of vertices in  Tai  the same, and adds  k  to labels of vertices in  Tbi .

If there is a tree  T  with  n  vertices  v0,v1,v2,,vn1 F(T)=n1i=0n1j=i+1d(vi,vj) ( d(vi,vj)  means the distance between the  vi  and  vj ).

For every  i(1im) , he wants to know  F(Ti) .
 

Input
There are multiple test cases(about  100 ).

For each test case, the first line contains one number  m(1m60) , the following are  m  lines. The  i -th lines contains five numbers  ai,bi,ci,di,li(0ai,bi<i,0li109) . It's guarenteed that there exists a vertex numbered  ci  in  Tai  and there exists a vertex numbered  di  in  Tbi .
 

Output
For each test case, print  F(Ti)  modulo  109+7  in the  i -th line.
 

Sample Input
  
  
3 0 0 0 0 2 1 1 0 0 4 2 2 1 0 3
 

Sample Output
  
  
2 28 216
 

Author
xudyh
 

Source
 


树上问题。

第0次合并时只有1棵一个节点的树,接着每次操作都把之前第 a[i]、b[i]次合并后的两棵树上编号c[i] d[i]的节点用长度为l[i]的一条边合并,第b[i]棵树上的点编号全部加上第a[i]棵树上节点的个数。就这样不断合并。


问你每次合并后所有树上路径的长度之和。

分治,把每棵树不断拆分成之前求过的树,并把每次搜到的答案用map记录下来。

具体实现详见代码注释。

好题啊!

十分简单易懂(...),我只看了一晚上就看懂了。


#include <cstdio>
#include <iostream>
#include <string.h>
#include <string> 
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#include <math.h>
#include <cmath>
#include <stack>
#include <utility>
#define mem0(a) memset(a,0,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))
#define pll pair<ll,ll>
typedef long long ll;
typedef long double ld;
using namespace std;
const int maxn=65,inf=0x3f3f3f3f;  
const ll llinf=0x3f3f3f3f3f3f3f3f,mod=1e9+7;   
const ld pi=acos(-1.0L);
ll a[maxn],b[maxn],c[maxn],d[maxn],l[maxn],size[maxn],msize[maxn],ans[maxn];
//记忆化搜索,把搜索过的答案储存起来 
map<pll,ll> q[maxn]; //存储第i棵树j到k的距离 
map<ll,ll> mp[maxn]; //存储第i棵树所有点到编号为k的节点的距离 

ll getlen(int i,ll now,ll k) {  //计算第i棵树中编号now到编号k的距离 
	if (now>k) swap(now,k);
	if (q[i].count(pll(now,k))) return q[i][pll(now,k)];
	if (k<size[a[i]]) //若两个节点都在第a[i]棵树当中,继续递归 
	    return q[i][pll(now,k)]=getlen(a[i],now,k);
	if (now>=size[a[i]]) //若两个节点都在第b[i]棵树当中,继续递归 
	    return q[i][pll(now,k)]=getlen(b[i],now-size[a[i]],k-size[a[i]]);
	//若节点不在同一棵子树,距离为now到a[i]经过l[i]到b[i]再到k的距离 
	return q[i][pll(now,k)]=(getlen(a[i],now,c[i])+l[i]+getlen(b[i],k-size[a[i]],d[i]))%mod;
}

ll getsum(int i,ll now) {   //计算第i棵树所有点到编号为now的距离和 
	if (mp[i].count(now)) return mp[i][now];
	if (now<size[a[i]]) 
	    return mp[i][now]=(getsum(a[i],now)+
		(l[i]+getlen(a[i],now,c[i]))*msize[b[i]]%mod+
		getsum(b[i],d[i]))%mod;
	//对第i棵树,同样地拆分成两棵树,计算now到所属子树所有点的距离、对面子树所有节点对答案的贡献 
	//其中,假设now属于第a[i]棵子树,对面子树b[i]所有节点对答案的贡献=b[i]所有节点到d[i]的距离+b[i]所有节点经过l[i]到a[i]再到c[i]的距离
	//若now属于b[i],则反之亦然 
	else 
	    return mp[i][now]=(getsum(b[i],now-size[a[i]])+
		(l[i]+getlen(b[i],now-size[a[i]],d[i]))*msize[a[i]]+
		getsum(a[i],c[i]))%mod;
}

int main() {
    int n;
	while (scanf("%d",&n)!=EOF) {
		int i,j;
		for (i=0;i<=60;i++) {
			mp[i].clear();q[i].clear();
		}
		q[0][pll(0,0)]=0;
		mp[0][0]=0;
		size[0]=msize[0]=1;ans[0]=0; 
		for (i=1;i<=n;i++) {
			scanf("%lld%lld%lld%lld%lld",&a[i],&b[i],&c[i],&d[i],&l[i]);
			size[i]=size[a[i]]+size[b[i]];
			msize[i]=size[i]%mod;
			ans[i]=ans[a[i]]+ans[b[i]]+
			((l[i]*msize[a[i]])%mod)*msize[b[i]]%mod+
			msize[b[i]]*getsum(a[i],c[i])%mod+
			msize[a[i]]*getsum(b[i],d[i])%mod;
			//合并之后答案为两棵树各自内部的答案贡献+路径端点分别在两棵子树上的贡献 
			//其中,路径端点分别在两棵树上的贡献=新增加的边l[i]的贡献+对方子树节点数*c[i],d[i]号节点到各自子树所有节点距离和 
			ans[i]%=mod;
			printf("%lld\n",ans[i]);
		}
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值