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;

}

 

 

你可能感兴趣的:(free)