【清橙 A1212】剪枝

题目链接

题目描述

给出一棵有根树。树有n个结点,被分别标记成 1到n的整数, 1号结点为根结点。
第 i(1≤i≤n)个结点的权值为 Wi。对于结点 i,它有 T i 个孩子,从左到右依次为Pi 1 ,
Pi 2 Pi Ti 。特别地,若i 号结点是叶结点,则T i =0。
我们对树进行深度优先搜索(DFS),每个点必须按从左到右的顺序访问每个
孩子,形成一个 DFS 序列,记作 Seq{Seq 1 ,Seq 2 ,…,Seq n }。对于两个叶结点 a、b,我们说它们是相邻的,当且仅当不存在另外的叶结点 c,在 DFS 序列中 c 在a、 b之间。换个方式讲,对于叶结点 a、 b、 c,记 Seq i =a,Seq j =b(i < j), 不存在 Seq k =c,使得i < k < j。
每对相邻的叶结点(a,b),都存在一个影响值。影响值定义为 a到b的路径上(不
包含 a、b的结点)的最大点权值。
定义一棵树的价值等于这棵树所有叶结点的权值之和减去每对相邻叶结点的影
响值。
当然,要是让你算这棵树的价值就太简单了。你的目标是对树进行一些剪枝,
使树的价值最大。剪枝的方式为:如果一个结点的孩子都是叶结点,就可以将它所有的孩子剪去。

题解

(终于写出了这个巨难题 QAQ)
把操作简单来讲就是去掉自己的子树使自己成为新的叶子节点,根本不用管什么自己的儿子全是叶子节点。
dp是显然的。
然后我们发现对于一个点来说,它对答案的贡献只受到它左右两边最近的一条链的影响。如果我们从左往右看的话就只用考虑它前面的和它最近的一条链。
假设我们考虑到了点u,对于它的一个祖先 A 来说,只有 A 的儿子 p (u是p的子孙) 的左兄弟才可能对点u对答案的贡献产生影响。因为再往上点 u 就也被剪掉了。
我们设 d p [ i ] 表示以i为一个新的叶子前面的产生的最大价值,然后按链转移(树形dp新姿势666)
细节巨多,自己看代码吧,就是维护两条链以及一些最值信息。
答案就在最右边拿一条链上。

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
const int N=1e5+22;
vector<int> a[N];
int dp[N];//将i作为标记点,考虑前面DFS序中能够产生的最大价值
int S1[N];//维护左链
int S2[N];//维护右链(左链用于更新右链)
int id[N];//每个点的在维护链的数组中的下标
int cnt;
int w[N];//点权
int I=0;
int ans;
int max_v[N];//在当前考虑的一条链中,节点u到交叉点的路径上的最大点权(不包括u)
int n;
inline void dfs(int u,int FA)//FA维护当前两条链的公共父亲在数组S1即左链中的下标
{
    if(FA==0){
        S1[++S1[0]]=u;id[u]=S1[0];dp[u]=w[u];
    }
    register int size=a[u].size();
    if(size){//要从叶子节点开始更新
        for(register int i=0;i<size;i++)
        {
            register int v=a[u][i];
            if(!i) {
                if(FA!=0) S2[++S2[0]]=v;//最左边的要用于被更新,为右链(但不能为一开始的左链)
                dfs(v,FA);//加入另外的点;
            }
            else S2[S2[0]=1]=v,dfs(v,id[u]);//开始新的右链
        }
        return;//只要把这条链更新一次即可
    }
    if(FA==0) return;//最左的链不考虑;
    register int mw1=w[S1[FA]];
    register int max_dp=-2147483647;
    register int mw2=mw1;
    register int k=FA;//左链上的点的指针
    for(register int i=1;i<=S2[0];i++){//直接更新这条右链
        while(k<S1[0]&&w[S1[k]]<=mw2)//这里k<S1[0],k指向当前考虑的点的父亲(最大值在右链的情况)
        {
            mw1=max(mw1,w[S1[k++]]);
            max_dp=max(max_dp,dp[S1[k]]);
            max_v[S1[k]]=mw1;
        }
        dp[S2[i]]=max_dp-mw2;//小于右链的最大权值时可以直接把这一段直接考虑;
        max_v[S2[i]]=mw2;
        mw2=max(mw2,w[S2[i]]);
    }
    while(k<S1[0])//更新后面剩下的
    {
        mw1=max(mw1,w[S1[k++]]);
        max_v[S1[k]]=mw1;
    }

    k=S1[0];max_dp=-2147483647;
    for(register int i=S2[0];i;--i){//(如果从分叉点开始考虑,计mw1为k到交叉点的路径上的最大点权,易知mw1递增)
        //这里一定要倒着写,因为正着写最后可能有不合法情况
        while(k>FA&&max_v[S1[k]]>=max_v[S2[i]])//最大值在左链的情况
        {
            max_dp=max(max_dp,dp[S1[k]]-max_v[S1[k]]);
            k--;
        }
        dp[S2[i]]=max(dp[S2[i]],max_dp);
    }
    for(register int i=1;i<=S2[0];i++)//更新完了一条链,这条被更新dp的链要纳入左链
    {
        dp[S2[i]]+=w[S2[i]];//最后再统计点权;
        S1[i+FA]=S2[i];id[S2[i]]=i+FA;
    }
    S1[0]=FA+S2[0];
    return ;
}
int main()
{
    n=read();
    for(register int i=1;i<=n;i++){
        w[i]=read();
        int t=read();
        for(register int j=1;j<=t;j++){
            register int x=read();
            a[i].push_back(x);
        }
    }
    Set(dp,-127/3);
    dfs(1,0);
    for(register int i=1;i<=S1[0];i++) ans=max(ans,dp[S1[i]]);//最后的右链
    printf("%d\n",ans);
}

猜你喜欢

转载自blog.csdn.net/element_hero/article/details/79392968