zoukankan      html  css  js  c++  java
  • 点分治——POJ 1741

    写的第一道点分治的题目,权当认识点分治了。

    点分治,就是对每条过某个点的路径进行考虑,若路径不经过此点,则可以对其子树进行考虑。

    具体可以看menci的blog:点分治

    来看一道例题:POJ 1741 Tree

    题目大意:扔给你一颗有权无根树,求有多少条路径的长度小于k;

    解题思路:先找出重心,用一次dfs处理出每个点到根的距离dis,然后将dis[]排序,用O(n)的复杂度处理出"过根且长度小于等于k的路径数目",删除根节点,对于每棵子树重复上述操作。

    注意要去重:

    像上面这样一个图,假设每条边的长度为1,即点到根的路径长等于点的深度,设k=4。此时dis[6]=2,dis[7]=2;dis[6]+dis[7]=4=k。但是当分治以③为根节点的子树时,dis[6]+dis[7]=2<k,这样6->7这条路径就被计算了两次,所以每次求完过根节点的符合条件的路径数之后,要减去在同一棵子树下的节点之间的符合条件的路径数。

    代码:

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 using namespace std;
     5 
     6 struct edge{
     7     int to,next,w;
     8 }e[20030];
     9 
    10 int root,ans,u,v,l,n,kk,ne,size;
    11 int head[10009],s[10009],d[10009],dis[10009],f[10009];
    12 //s[k]为k的子节点数目,dis[k]为k到根节点的距离
    13 bool b[10009];
    14 
    15 void add(int a,int b,int c){
    16     e[++ne].to=b; e[ne].w=c; e[ne].next=head[a]; head[a]=ne;
    17 }
    18 
    19 void getroot(int k,int fa){
    20     int v,i;
    21     s[k]=1;
    22     f[k]=0;
    23     for(i=head[k];i!=-1;i=e[i].next){
    24         v=e[i].to;
    25         if(v!=fa&&!b[v]){
    26             getroot(v,k);
    27             s[k]+=s[v];//递归求子节点数目
    28             f[k]=max(f[k],s[v]);
    29         }
    30     }
    31     f[k]=max(f[k],size-s[k]);//dp求重心
    32     if(f[k]<f[root])root=k;
    33 }
    34 
    35 void getdis(int k,int fa){
    36     int i,v;
    37     d[++d[0]]=dis[k];//储存距离以便排序
    38     for(i=head[k];i!=-1;i=e[i].next){
    39         v=e[i].to;
    40         if(v!=fa&&!b[v]){
    41             dis[v]=dis[k]+e[i].w;//dfs求距离
    42             getdis(v,k);
    43         }
    44     }
    45 }
    46 
    47 int clac(int k,int init){
    48     int i,j,ret=0;
    49     d[0]=0;
    50     dis[k]=init;
    51     getdis(k,0);
    52     sort(d+1,d+1+d[0]);
    53     for(i=1,j=d[0];i<j;)
    54         if(d[i]+d[j]<=kk)ret+=j-i++;//计算路径数
    55         else j--;
    56     return ret;
    57 }
    58 
    59 
    60 void work(int k){
    61     int v,i;
    62     ans+=clac(k,0);//计算路径数并加在ans上
    63     b[k]=true;//标记重心为删除
    64     for(i=head[k];i!=-1;i=e[i].next){
    65         v=e[i].to;
    66         if(!b[v]){
    67             ans-=clac(v,e[i].w);//去重
    68             f[0]=size=s[v];
    69             getroot(v,root=0);//更新重心
    70             work(root);//对子树求解
    71         }
    72     }
    73 }
    74 
    75 int main(){
    76     int i;
    77     while(scanf("%d%d",&n,&kk)==2){
    78         if(n==0&&kk==0)break;
    79         for(i=1;i<=n;i++){
    80             head[i]=-1;
    81             b[i]=0;
    82         }
    83         ne=0;
    84         for(i=1;i<=n-1;i++){
    85             scanf("%d%d%d",&u,&v,&l);
    86             add(u,v,l);
    87             add(v,u,l);//注意要连双向边
    88         }
    89         root=0;
    90         f[0]=size=n;
    91         getroot(1,0);//求重心
    92         ans=0;
    93         work(root);
    94         printf("%d
    ",ans);
    95     }
    96     return 0;
    97 }

    总体上感觉点分治不是很难理解,只要对题目有思路应该能写出来。(orz就是写代码经常出错!b[v]写成!b[k]之类的还查不出来)

  • 相关阅读:
    引用传参
    VS2017 用MySQL Connector 链接 MySQL时 getString异常问题
    Matlab学习日记第3天
    Matlab学习日记第2天
    Matlab学习日记第1天
    c#加密解密方法
    DataGridView添加行号
    c#带参数数组链接数据库方法
    2021/5/27
    2021/5/14
  • 原文地址:https://www.cnblogs.com/y-m-y/p/5779610.html
Copyright © 2011-2022 走看看