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;
}
原文地址:https://www.cnblogs.com/yefeng1627/p/2849709.html