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; }
解题代码
#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; }