• spoj 1825. Free tour II 基于树的点分治


    题目大意

      一颗含有N个顶点的树,节点间有权值, 节点分为黑点和白点.问题是 找一条黑点数量不超过K个的最大路径.

    解题思路:

      因为路径只有 过根节点以及不过根节点, 所以我们可以通过找寻树重心分治下去.

      问题就退化成了处理:

        对于当前以 x 为根的树, 其最大路径

        对于X的所有直接子节点, 定义函数 G( I, J ) 表示子节点 I为根的树不超过J个黑点的最大路径.

        定义函数 dep( i ) 表示以 i为根的树,最多黑点数量.

        则结果为 ans = MAX{ G(u,L1) + G(v,L2) }    ( u != v, 且 L1+L2 <= K - (当前根节点x为黑点则为1,否则为0)  )

        或者 ans = MAX{ G( i, j ) }  这个是当不过根节点的情况.

      因为 N = 200000 , 两两子节点枚举肯定TLE.

      我们可以构造一个函数 F( j ), 表示 [1-j] 个黑点的最大路径值

      对于 以X为根的树, 其子节点 v树, 的函数为 G( v, dep[v] )

      枚举  dep[i] , 然后 结果即为 MAX{ ans, G(v,dep[i] + F( K-dep[i] )  }   

      因为路径与子节点顺序无关,我们可以将其以 黑点数量dep 排序. 

      F( v ) 即为左边 [1-v]个子节点合并的状态.

      G( v ) 即为当前字节点v节点的状态.

      处理完当前子节点v后, 合并其进入到F中即可.

      要特别注意, 虽然边权有负数, 我们可以只取一个顶点的路径, 所以结果最小为0 .

      所以 ans 初始化要为 0, 不可为无穷小

      详细见代码注释及其分析

    注释代码
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<string.h>
    #include<math.h>
    #include<algorithm>
    #include<vector>
    using namespace std;
    const int maxn=200100;
    const int INF=2000100010;
    struct edge
    {
           int u,v,val,next;
    }et[maxn*2];
    bool was[maxn];
    int has[maxn];
    int eh[maxn],tot;
    bool cur[maxn];
    int f[maxn],g[maxn];
    void add(int u,int v,int val)
    {
           et[tot].u=u;et[tot].v=v;et[tot].val=val;
           et[tot].next=eh[u];eh[u]=tot;tot++;
    }
    void addedge(int u,int v,int val)
    {
           add(u,v,val);add(v,u,val);
    }
    int n,K,m;
    int mi,hasnode;
    int min(int a,int b) {return a<b?a:b;}
    int max(int a,int b) {return a>b?a:b;}
    void getroot(int x,int fa,int &r) //treedp找重心
    {
           has[x]=1; //has[x]存储以x为根节点的子树,节点数量和
           int ma=0; //x的最大子树节点数量
           for(int i=eh[x];i!=-1;i=et[i].next)
           {
                  if(et[i].v==fa||cur[et[i].v]) continue;
                  getroot(et[i].v,x,r);
                  has[x]+=has[et[i].v];
                  if(has[et[i].v]>ma) ma=has[et[i].v];
           }
           if(ma<hasnode-has[x]) ma=hasnode-has[x];
           if(ma<mi) {mi=ma;r=x;}
    }
    int hs;
    void gettn(int x,int fa)   //数子结点的个数,顺便数埋子结点是“黑”的个数
    {
           hasnode++;
           if(was[x]) hs++;
           for(int i=eh[x];i!=-1;i=et[i].next)
           {
                  if(et[i].v==fa||cur[et[i].v]) continue;
                  gettn(et[i].v,x);
           }
    }
    void getg(int x,int fa,int h,int val)   //对x为根结点的树,求函数g
    {
           if(g[h]<val) g[h]=val;
           for(int i=eh[x];i!=-1;i=et[i].next)
           {
                  int v=et[i].v;
                  if(fa==v||cur[v]) continue;
                  if(was[v]) getg(v,x,h+1,val+et[i].val);
                  else getg(v,x,h,val+et[i].val);
           }
    }
    struct tt   //纯粹为了sort开的结构体
    {
           int v,han; // v为子树根, han为子树黑点数量
           int beval; // 子树v到其根节点边权
    }tb[maxn*4];
    int cmp(tt a,tt b)
    {
           return a.han<b.han;//按黑点数量从小到大排序
    }
    int ans;
    void play(int x,int fa,int rec) // x重心根节点,fa父节点, 存储信息开始下标rec
    {
           int i,j,k,hrec=rec; //tb[] 中 rec~hrec是存储了当前所有子结点的缓冲区。
           for(i=eh[x];i!=-1;i=et[i].next)
           {
                  int v=et[i].v;
                  if(fa==v||cur[v]) continue;
                 
                  //初始化子树v的节点数量hasnode, 辅助变量mi, 黑点数量 hs
                  hasnode=0;mi=INF;hs=0; 
                  
                  int root; //重心
                  
                  // 统计子树v,节点总数hasnode, 黑点总数hs 
                  gettn(v,x);
                 
                  // 寻找子树v的重心 root  
                  getroot(v,x,root); 
                  
                  // 存储子树信息
                  // han 为子树v黑点数量
                  // v 为子树根名
                  // beval 为子树v到根节点x的权值
                  // 对于当前以x为根的树,其保存的区间 [rec, hrec] 为这一颗树上所有子树的信息
                 
                  tb[hrec].han=hs;tb[hrec].v=v;  
                  tb[hrec].beval=et[i].val;hrec++;
                  cur[root]=1; //标记已找出重心root
                  play(root,x,hrec); //递归子树root,其根节点为x,使用数组下标从hrec开始
                 
                  //回溯回来后,需要处理当前子树,当前子树内的标记需要撤销
                  cur[root]=0; //回溯,取消标记重心root
           }                                 //直到以上的分治步骤都和PKU 1741的TREE差不多。
           
           //将x的所有子节点按其对应子树黑点数量,从小到大,排序 
           sort(tb+rec,tb+hrec,cmp); 
           
           
           int now=j;
           int kk=K; //当前子树内路径最多黑点数
           if(was[x]) kk--;    //注意如果根是黑点K--
           int ft=-1;   //当前f函数的大小
           
           //遍历根节点x的所有子节点,每个子节点保存了其到根节点长度, 以当前子节点为根的子数最多黑点数量
           // han 为子树v黑点数量
           // v 为子树根名
           // beval 为子树v到根节点x的权值
           // 对于当前以x为根的树,其保存的区间 [rec, hrec] 为这一颗树上所有子树的信息
           for(i=rec;i<hrec;i++) 
           {
                  int v=tb[i].v; //子节点
                  int hasn=tb[i].han; //子树中最大黑点数量
                  if(fa==v||cur[v]) continue;
                 
                  //初始化g,g[i]表示当前子树小于等于j个黑点的最大路径值
                  for(j=0;j<=hasn;j++) g[j]=-INF;
                 
                  //使用递归函数getg,递归获取 以v为根,父节点为x 的子树 
                  // 从x节点到 子树v上任意节点 只有i个黑点的最大路径值 
                  if(was[v]) getg(v,x,1,tb[i].beval);
                  else       getg(v,x,0,tb[i].beval);
                 
                  // 每次子树v时,初始化ma为无穷小,用来更新g函数
                  int ma=-INF;
                  if(i==rec)   //一开始f没东西,赋初值。
                  {
                         for(j=0;j<=hasn;j++) // 枚举黑点数量
                         {
                                //若j大于最多节点数量k时
                                if(j>kk) break;
                                ma=max(ma,g[j]);
                                
                                f[j]=ma; // f[j]表示 [1,j]个黑点的最大路径值 
                         }
                         // 当前黑点数量函数值 f的最大值 
                         ft=min(hasn,kk);
                  } 
                  else
                  {
                         for(j=0;j<=hasn;j++)   // 找:以v左边的子树和v的子树之间,过根结点的路径。
                         {
                                // 若当前黑点数量j 大于最大要求黑点数量kk 
                                if(j>kk) break;
                               
                                // 若v子树中j个黑点的最大路径,属于最大路径,
                                // 则此时,还可包含 最多temp个黑点
                                int temp=kk-j; 
                                // 若此时 可放置黑点数量多余 已有数量ft,则放置ft即可    
                                if(temp>ft) temp=ft;
                                
                                // 若 子树v中包含j个黑点的最大路径等于无穷小,或 左边树temp黑点最大路径小于无穷小则 不纳入计算
                                if(f[temp]==-INF||g[j]==-INF) continue;
                              
                                // 最终结果与当前组合取最值
                                ans=max(ans,g[j]+f[temp]);
                         }
                        
                         
                         //把v的子树合并进去左边的所有子树中。 
                         for(j=0;j<=hasn;j++) 
                         {
                                // 若当前黑点数量多与 最大容量则结束 
                                if(j>kk) break;
                               
                                ma=max(ma,g[j]);
                                //注意,这里只有当 左边子树的黑点数量大于目前子树v此时黑点j的时候     
                                if(ft>=j) ma=max(ma,f[j]);
                                
                                //将子树v合并到 左边子树中去,更新 前j个黑点最大路径值    
                                f[j]=ma;
                         }
                        
                         // 因为首先将所有子节点依据黑点数量从小到大排序了,所以我们处理每个新的子树时
                         // 左边集合,黑点数上界为 min( hasn, kk )
                         ft=min(hasn,kk);
                  }
           }
           if(ft>=0) ans=max(ans,f[min(ft,kk)]);
    }
    int main()
    {
           int i,j,k;
    //     freopen("in.txt","r",stdin);
           scanf("%d%d%d",&n,&K,&m);
           for(i=0;i<=n;i++) {was[i]=cur[i]=0;eh[i]=-1;} //初始化,was[i]黑点,cur[i]目前根节点,eh[i]链表头
           tot=0; 
           for(i=0;i<m;i++)
           {
                  int temp;
                  scanf("%d",&temp);was[temp]=1; //标记黑点
           }
           for(i=0;i<n-1;i++)
           {
                  int u,v,val;
                  scanf("%d%d%d",&u,&v,&val);
                  addedge(u,v,val); //静态链接添加E( u, v, val )
           }
           mi=INF;   //辅助变量,用于寻找重心 
           int root;    //重心
           hasnode=n;getroot(1,0,root); // hasnode当前子树总节点数量 , 函数getroot获取重心,其中root为引用
           cur[root]=1; // 标记已选重心
           ans=0;    //初始化ans
           play(root,0,0); //求以root为根的子数,不超过K个黑点的最大路径长度
           printf("%d\n",ans);
           return 0;
    }

    解题代码

    View Code
    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    
    #define MAX(a,b) (a)>(b)?(a):(b)
    #define MIN(a,b) (a)<(b)?(a):(b)
    const int N = 200100;
    const int inf = 0x7fffffff;
    
    struct Edge{
        int v, c, nxt;
    }edge[N<<2];
    
    int head[N], idx;
    int n, m, K;
    
    struct node{
        int sum, max;
    }p[N];
    struct tree{
        int h, v, c;
        bool operator < (tree tmp) const{
            return h < tmp.h;    
        }
    }Q[N<<2];
    int G[N], F[N], ans;
    int black[N];
    int Maxval, hasnode, dep;
    bool cur[N];
    
    
    void addedge( int u, int v, int c )
    {
        edge[idx].v = v; edge[idx].c = c; 
        edge[idx].nxt = head[u]; head[u] = idx++;
    
        edge[idx].v = u; edge[idx].c = c;
        edge[idx].nxt = head[v]; head[v] = idx++;
    }
    void Input()
    {
        memset( head, 0xff, sizeof(head) );
        idx = 0;
        memset( black, 0, sizeof(black) );    
        int u, v, c;
        for(int i = 0; i < m; i++)
        {
            scanf("%d", &u);
            black[u] = 1;
        }
        for(int i = 0; i < n-1; i++)
        {
            scanf("%d%d%d",&u,&v,&c);
            addedge( u, v, c );
        }
    }
    
    void GetRoot( int u, int pre, int &rt )
    {
        p[u].sum = 1; p[u].max = 0;    
        for(int i = head[u]; ~i; i = edge[i].nxt )
        {
            int v = edge[i].v;
            if( (v!=pre) && (!cur[v]) )
            {
                GetRoot( v, u, rt );
                p[u].max = MAX( p[u].max, p[v].sum );
                p[u].sum += p[v].sum;
            }
        }
        p[u].max = MAX( p[u].max, hasnode-p[u].sum );
        if( p[u].max < Maxval )
        {
            Maxval = p[u].max;
            rt = u;
        }
    }
    void GetHasnode( int u,int pre )
    {
        dep += black[u];
        hasnode++;
        for(int i = head[u]; ~i; i = edge[i].nxt )
        {
            int v = edge[i].v;
            if( (v!=pre) && (!cur[v]) )
                GetHasnode( v, u ); 
        }
    }
    void GetG( int u, int pre, int hs, int c )
    {
        G[hs] = MAX( G[hs], c );
        for(int i = head[u]; ~i; i = edge[i].nxt )
        {
            int v = edge[i].v;
            if( (v!=pre) && (!cur[v]) )
                GetG( v, u, hs+black[v], c+edge[i].c );    
        }
    }
    void solve(int x, int pre, int rec)
    {
        int hrec = rec;
        int kk = K - black[x];
    
        for(int i = head[x]; ~i; i = edge[i].nxt )
        {
            int v = edge[i].v;
            if( (v != pre) && (!cur[v]) )
            {
                // init     
                hasnode = 0; Maxval = inf; dep = 0; 
                int rt;
                
                // Get hasnode and dep;
                GetHasnode( v, x );
                GetRoot( v, x, rt );
                // save the child informations    
                Q[hrec].h = dep; Q[hrec].v = v; Q[hrec++].c = edge[i].c;
            
                cur[rt] = true;
                solve( rt, x, hrec );
                cur[rt] = false;
            }
        }
        sort( Q+rec, Q+hrec );
    
        //test
        //printf("rt = %d\n", x );
        int fuck , maxdep = -1;
        for(int i = rec; i < hrec; i++)
        {
            int v = Q[i].v, hs = Q[i].h;    
            if( (v==pre) || (cur[v]) ) continue;    
            for(int j = 0; j <= hs; j++)
                G[j] = -inf;
            fuck = -inf;
            //test
            //printf("v = %d, hs = %d\n", v, hs );    
            GetG( v, x, black[v], Q[i].c );
            //test
            //for(int j = 0; j <= hs; j++)
            //    printf("%d ", G[j] ); printf("\n\n");
            if( i == rec )
            {//first init F function
                for(int j = 0; j <= hs; j++)
                {
                    if( j > kk ) break;
                    fuck = MAX( fuck, G[j] );         
                    F[j] = fuck;    
                }
                maxdep = MIN( hs, kk );    
            }
            else{
                // Get the max loads black point less than kk    
                for(int j = 0; j <= hs; j++)
                {
                    if( j > kk ) break;
                    int tmp = kk-j;
                    if( tmp > maxdep ) tmp = maxdep;
                    if( (G[j]==-inf) || (F[tmp]==-inf) ) continue;    
                    ans = MAX( ans, G[j]+F[tmp] );
                }
                // union
                for(int j = 0; j <= hs; j++)
                {
                    if( j > kk ) break;
                    fuck = MAX( fuck, G[j] );
                    if( j <= maxdep ) fuck = MAX( fuck, F[j] );    
                    F[j] = fuck;    
                }
                maxdep = MIN( hs, kk );    
            }    
        }
        // only one child's way get ans 
        if( maxdep >= 0 ) ans = MAX( ans, F[ MIN(maxdep,kk) ] );
    
        //getchar();getchar();
    }
    int main()
    {
        scanf("%d%d%d", &n,&K,&m); 
        {
            Input();
            
            int rt;
            Maxval = inf;     
            hasnode = n;
            GetRoot( 1, 0, rt );    
            memset(cur,0,sizeof(cur));
            cur[rt] = true;
    
            ans = 0;
            solve( rt, 0, 0 );        
            
            printf("%d\n", ans );
        }
        return 0;
    }
  • 相关阅读:
    ajax语法结构
    数据传输编码模式的解析
    choice参数与MTV和MVC
    only和defer与select_related和prefetch_Related(面试重点)
    聚合查询、分组查询、 F与Q查询
    多表查询
    单表查询
    模板的继承和导入
    模板语法的传值和取值
    IOC依赖注入简单实例
  • 原文地址:https://www.cnblogs.com/yefeng1627/p/2849709.html
Copyright © 2020-2023  润新知