jzoj5336 【NOIP2017提高A组模拟8.24】提米树 (dfs序dp,奇异姿势dp)

39 篇文章 0 订阅
21 篇文章 0 订阅

题面

xxx

分析

剪枝的意思就是你可以任意选点作为叶子。(前提是他子树不选)
比赛的时候有一种60分的n^2 log n做法,就是在dfs序上直接dp.
但是正解比较奇怪,先画颗树出来看看,就会发现根到真·叶子的路径上有且只有一个被选为叶子。于是我们考虑设一种玄学的dp。 令 f[i] 为在dfs序上,当前最后一个叶子选的是i的最大价值。
想想能更新i的点有哪些。 由于要保证每条到叶子的路径上都有选中的,那么当前状态要么没有意义( 选他的祖先 ),要么就从相邻的叶子节点到二者lca (不含)这条路径上的点更新过来。 (看不懂的就看图吧)
这里写图片描述

这样每次枚举两个相邻叶子,然后暴力枚举从哪个绿点转移的话是O(n^2)的。
没有被转移到的点价值就是本身。 (因为他不需要接盘,只选自己就行)

在优化之前,先证明一个结论: 相邻叶子节点的路径长度(也就是点数-1)不会超过2n.
考虑一条边会被选到多少次,当然最多两次了,子树外到内 与 内到外各一次。子树内都选不到他。

这样从上到下更新要更新的点,lca~当前要更新的点 的路径mx当然是递增的,然后左边决策集合中的mx也是递增的,维护一个指针now表示当前决策集合(按深度排序,感受一下) 1..now-1是用自己那边的mx,然后now..lca是用 lca~当前要更新的点上的mx。
这样维护一个前缀和+后缀和,快速计算一下两部分的最优值就可以O(n)做了。 (细节比较多但为什么有大佬能打进1000byte

Demo

#include <cstdio>
#include <iostream>
#include <cstring>
#define max(a,b) ((a)<(b)?(b):(a))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int N=1e5+10,INF=2e9;
int final[N],nex[N],to[N],tot,dep[N];
int n,a[N];
int fa[N],ch[N];

int f[N];
int s[N],td[N],pre[N],sufmxa[N],sufmxf[N];
int tmp[N];

void link(int x,int y) {to[++tot]=y, nex[tot]=final[x], final[x]=tot;}
void dfs(int x) {
    dep[x]=dep[fa[x]]+1;
    if (!final[x]) ch[++ch[0]]=x; else
    for (int i=final[x]; i; i=nex[i]) fa[to[i]]=x, dfs(to[i]);
}
int init(int x,int y) {
    int tx=x,ty=y,g=0;
    s[0]=td[0]=0;
    while (dep[ty]>dep[x]) td[++td[0]]=ty,ty=fa[ty];
    while (dep[tx]>dep[y]) {
        if (f[tx]==f[0]) f[tx]=a[tx];
        s[++s[0]]=tx;
        tx=fa[tx];
    }
    while (tx!=ty) {
        if (f[tx]==f[0]) f[tx]=a[tx];
        td[++td[0]]=ty, s[++s[0]]=tx;
        tx=fa[tx],ty=fa[ty];
    }
    s[++s[0]]=g=tx;
    pre[0]=sufmxa[s[0]]=sufmxf[s[0]]=-INF;
    if (f[g]==f[0]) f[g]=a[g]; 
    for (int i=s[0]-1; i; i--) {
        sufmxf[i]=max(sufmxf[i+1],f[s[i]]);  //max  f[a..g]
        sufmxa[i]=max(sufmxa[i+1],a[s[i+1]]);//max  a[fa[a]..g]
    }
    for (int i=1; i<s[0]; i++) pre[i]=max(pre[i-1],f[s[i]] - sufmxa[i]); //max ans[1..i]
    return g;
}
int main() {
    freopen("3.in","r",stdin);
//  freopen("3.out","w",stdout);
    cin>>n;
    for (int i=1; i<=n; i++) {
        int k=0;
        scanf("%d %d",&a[i],&tmp[0]);
        for (int j=1; j<=tmp[0]; j++) scanf("%d",&tmp[j]);
        for (int j=tmp[0]; j; j--) link(i,tmp[j]);
    }
    memset(f,128,sizeof f);
    dfs(1);
    for (int i=1; i<ch[0]; i++) {
        int x=ch[i],y=ch[i+1],g=init(x,y),now=s[0],rmx=0;
        for (int d,j=td[0]; j; j--) {
            d=td[j], rmx=max(rmx,a[fa[d]]);
            while (now>1 && sufmxa[now-1]<=rmx) now--;
            f[d]=max(pre[now-1], sufmxf[now] - rmx)+a[d];
        }
    }

    int ans=0;
    for (int i=ch[ch[0]]; i; i=fa[i]) {
        if (f[i]==f[0]) f[i]=a[i];
        ans=max(ans,f[i]);
    }
    printf("%d\n",ans);

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值