• [洛谷P4178] Tree


    这道题是比较裸的点分治。

    洛谷传送门

    poj1741 传送门

    点分治是树分治的一种,据说是最常用的。

    除了点分治,还有边分治、链分治等等。

    点分治的话,每次找到一个点作为根,把树拆成几个部分。

    先统计与根有关的答案,再在几个子树内继续拆分下去。

    显然那个点最好选树的重心。

    具体到这道题,每次选完根以后,先算一遍距离。

    如果n^2暴力统计距离,太慢了。

    我们排个序之后,双指针扫一遍。

    如果现在dis[l]+dis[r]大于k,那么实际上r就没有用了,因为l++之后的dis[l]肯定比现在的dis[l]大。

    所以某个r没有用了,我们就可以r--了。

    每一次统计的答案不仅仅是跨越根的路径(如图):

    也包含了不跨根的非法路径(如图):

    所以我们再在每个子树中分别统计一遍,减掉就行了。

    然后再递归进行下一层的分治。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 
     6 typedef long long ll;
     7 
     8 int n,k,ans;
     9 int hd[40005],to[80005],nx[80005],len[80005],ec;
    10 
    11 void edge(int af,int at,int el)
    12 {
    13     to[++ec]=at;
    14     len[ec]=el;
    15     nx[ec]=hd[af];
    16     hd[af]=ec;
    17 }
    18 
    19 int rt,tp,tot;
    20 ll buf[40005],dis[40005];
    21 int sz[40005],mx[40005];
    22 int del[40005];
    23 
    24 void weigh(int p,int fa)
    25 {
    26     sz[p]=1;mx[p]=0;
    27     for(int i=hd[p];i;i=nx[i])
    28     {
    29         int tar=to[i];
    30         if(tar==fa||del[tar])continue;
    31         weigh(tar,p);
    32         sz[p]+=sz[tar];
    33         mx[p]=max(mx[p],sz[tar]);
    34     }
    35     mx[p]=max(mx[p],tot-sz[p]);
    36     if(mx[p]<mx[rt])rt=p;
    37 }
    38 
    39 void dfs(int p,int fa)
    40 {
    41     buf[++tp]=dis[p];
    42     for(int i=hd[p];i;i=nx[i])
    43     {
    44         int tar=to[i];
    45         if(tar==fa||del[tar])continue;
    46         dis[tar]=dis[p]+len[i];
    47         dfs(tar,p);
    48     }
    49 }
    50 
    51 int count(int p,int d0)
    52 {
    53     dis[p]=d0;tp=0;
    54     dfs(p,0);
    55     sort(buf+1,buf+tp+1);
    56     int l=1,r=tp,ret=0;
    57     while(l<r)
    58     {
    59         if(buf[l]+buf[r]>k)r--;
    60         else ret+=r-(l++);
    61     }
    62     return ret;
    63 }
    64 
    65 void conquer(int p)
    66 {
    67     ans+=count(p,0);
    68     del[p]=1;
    69     for(int i=hd[p];i;i=nx[i])
    70     {
    71         int tar=to[i];
    72         if(del[tar])continue;
    73         ans-=count(tar,len[i]);
    74         tot=sz[tar];rt=0;
    75         weigh(tar,0);
    76         conquer(rt);
    77     }
    78 }
    79 
    80 int main()
    81 {
    82     scanf("%d",&n);
    83     for(int i=1;i<n;i++)
    84     {
    85         int ff,tt,vv;
    86         scanf("%d%d%d",&ff,&tt,&vv);
    87         edge(ff,tt,vv);
    88         edge(tt,ff,vv);
    89     }
    90     scanf("%d",&k);
    91     tot=mx[0]=n;
    92     weigh(1,0);
    93     conquer(rt);
    94     printf("%d",ans);
    95     return 0;
    96 }

     连了双向边但是没有开双倍数组导致凉凉......

    但是洛谷的评测姬告诉我是MLE而不是RE。

    所以我盯着,那个算法实现完全正确的程序,“查错”了将近一个小时......

  • 相关阅读:
    butter
    医院设置
    NOIP 2000 进制转换
    图的M 着色问题
    闭合区域面积统计
    字符序列
    装载问题
    n皇后问题
    跳马问题
    数独
  • 原文地址:https://www.cnblogs.com/cervusy/p/9996123.html
Copyright © 2020-2023  润新知