POJ 1741 Tree (树分治)

题意:给定一棵树,求树上两点间距离小等于K的所有方案数。

  这题怎么想?(我猜一定会有人想lca)

  如果这么想,最终一定会顿悟自己陷入了一个复杂度的陷阱。因为这样就是枚举+大暴力。求了LCA还不如不求直接n^2求两点距离呢。。。

  那这题我们怎么办。树形dp?其实差不多,就是树形dp,但是,怎么设计是一个问题,稍有不慎就会变成大暴力。

  对于根,可以用一个小小的dp求出各点到跟的距离。然后通过排序可以算出符合要求的答案,但是答案肯定不可能仅仅过这一个根节点,那怎么办,我们可以考虑把所有点都当根节点,对于每个根节点求出必经这个根节点的符合题意的路径数。我这里用了必经两个字,显然按照我们之前排序那么求会出现不必经的劣质结果,我们这时就要对结果进行删重,在此可以用图示说明:

 

  这个时候我曲线指明的这两个点显然就是不必经根节点的劣质答案,就要删去,只需要枚举当前根节点的子树,这时就不只是满足小于k的问题了,而是要小于(k-连接子树与根的那条边),这样的结果我们之前肯定记录过,而且不会删去其他不多余的答案。。。

  诶我忘了解释为啥删重,只是说了个“显然”,其实挺显然的,再上图:

  我们求过答案的“根节点”,就当他不存在了,现在新的根节点,像上面这种情况,就是我们之前求重了的路径,这时才该他起作用呢!

  我们不断这样做会出现一个问题,如果树是一条链,那我们就爆n^2了,怎么处理一下,看上图恐怕大家已经发现端倪了,我们每次都找当前剩余树的重心,这样就能保证子树规模相当,从而不被卡。

  据说这方法叫树分治。

以上。

 

 1 //昨夜小楼又东风,珠帘泛婆娑湿衣袖 
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<algorithm>
 5 #include<vector>
 6 #include<cstring>
 7 using namespace std;
 8 const int MAXN=10009;
 9 struct node{
10     int y,z,nxt;
11 }edge[MAXN*2];int head[MAXN];bool vis[MAXN];
12 int n,k,siz,s[MAXN],f[MAXN],rt,d[MAXN];
13 vector<int>dep;int indx=0,cnt=0,m,ans;
14 void add(int x,int y,int z){
15     edge[++indx].y=y;edge[indx].z=z;edge[indx].nxt=head[x];head[x]=indx;
16 }
17 void getroot(int x,int fa){
18     s[x]=1;f[x]=0;
19     for(int i=head[x],y;~i;i=edge[i].nxt)
20     if((y=edge[i].y)!=fa&&!vis[y]){
21         getroot(y,x);s[x]+=s[y];f[x]=max(f[x],s[y]);
22     } f[x]=max(f[x],siz-s[x]);
23     if(f[x]<f[rt]) rt=x; 
24 }
25 void getdep(int x,int fa){
26     dep.push_back(d[x]);s[x]=1;
27     for(int i=head[x],y;i!=-1;i=edge[i].nxt){
28         if((y=edge[i].y)==fa||vis[y]) continue;
29         d[y]=d[x]+edge[i].z;getdep(y,x);s[x]+=s[y];
30     }
31 }
32 int calc(int x,int init){
33     dep.clear();d[x]=init;getdep(x,0);
34     sort(dep.begin(),dep.end());int tot=0;
35     for(int l=0,r=dep.size()-1;l<r;)
36     if(dep[l]+dep[r]<=m) tot+=r-l++;
37     else r--; return tot;
38 }
39 void work(int x){
40     ans+=calc(x,0);vis[x]=true;
41     for(int i=head[x],y;i!=-1;i=edge[i].nxt)
42         if(!vis[y=edge[i].y]){ 
43             ans-=calc(y,edge[i].z);
44             f[0]=siz=s[y];
45             getroot(y,rt=0);work(rt);
46         }
47 }
48 int main(){
49     while(~scanf("%d%d",&n,&m)&&(n||m)){
50         memset(head,-1,sizeof(head));indx=0;
51         memset(vis,0,sizeof(vis));dep.clear();
52         for(int i=1,x,y,z;i<n;i++){
53             scanf("%d%d%d",&x,&y,&z);
54             add(x,y,z);add(y,x,z);
55         } f[0]=siz=n;
56         getroot(1,rt=0);ans=0;
57         work(rt);printf("%d\n",ans);
58     } return 0;
59 }
60 //恰似故人远来葬花落。 
树分治

 

转载于:https://www.cnblogs.com/Alan-Luo/articles/9342345.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值