[JZOJ5336] 提米树

题目大意

给一颗n个点的以1为根树,点权i值为a[i]。
定义相邻的叶子(x,y):x,y都是叶子节点(无儿子),而且在dfs序中,x,y之间没有其他叶子节点。
定义一棵树的贡献:所有叶子节点的权值减去所有相邻叶子的特殊贡献。
(x,y)的特殊贡献定义为,x到y路径上,除了x,y之外所有点的权值的最大值。
定义剪枝:如果一个点z的儿子都是叶子,剪枝可以把z的所有儿子去掉。
不限制剪枝次数,求树的最大贡献。
n<=105109 n <= 10 5 , 答 案 保 证 不 超 过 10 9

分析

题意十分长,原题面更是掺杂了其他东西,审题要小心。
每次剪枝相当于把原来的叶子浓缩成他们的父亲,那么考虑到剪枝的特点,可以发现,对于原来的每个叶子x,1到x的路径必须要有一个点要被保留为叶子。
我们考虑使用DP,当然你也可以先尝试树形DP然后发现他的可行性。
设f[i]表示考虑了dfn为1~i的节点,此时这颗树(有可能未完成)的贡献。考虑转移,我们顺着DFS序,考虑从一个叶子到另外一个叶子的路径上所有点的转移/被转移。
这里写图片描述
(盗图,懒得做了)
可以看出是lca为分割点的两部分,左边的部分是被转移的点,右边是要转移到的点,lca及以上是不涉及转移的。我们暴力枚举两边的点进行转移,f[new]=max{f[old]-cost}+a[new],cost即为路径上权值最大值。这样就是 O(n2) O ( n 2 ) 的了。
考虑优化,我们设h[old]表示从father[old],即old的父亲,到lca的路径的最大权值,H[new]表示从father[new]到lca路径的最大权值,那么原式变成f[new]=max{f[old]-max(h[old],H[new])}。注意到h和H的单调性,对于一个点new,把所有old点分为h[old]<=H[new]和h[old]>H[new]两部分,那么两部分的点都是连续的,就可以分开维护来更新f[new]了。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<set> 
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
const int N=2e5+5,mo=1e9+7,mx=1e9;
int dis[N],fa[N],b[N],nxt[N],fst[N],tt,a[N],d[N],e[N],f[N],g[N],h[N],w[N],mxf,H,r,i,j,x,y,n,ans;
void cr(int x,int y)
{
    tt++;
    b[tt]=y;
    nxt[tt]=fst[x];
    fst[x]=tt;
}
void dfs(int x)
{
    //dfn[x]=++td;
    dis[x]=dis[fa[x]]+1;
    if (!fst[x])
        d[++d[0]]=x;
    for(int p=fst[x];p;p=nxt[p])
        dfs(b[p]);
}
int main()
{
    freopen("t3.in","r",stdin);
//  freopen("t3.out","w",stdout);
    scanf("%d\n",&n);
    fo(i,1,n)
    {
        scanf("%d %d",a+i,&x);
        fo(j,1,x)
        {
            scanf("%d",&y);
            cr(i,y);
            fa[y]=i;
        }
    }
    dfs(1);
    x=d[1];
    while (x) f[x]=a[x],x=fa[x];
    fo(i,1,d[0]-1)
    {
        w[0]=e[0]=0;
        x=d[i];y=d[i+1];
        while (dis[x]>dis[y]) e[++e[0]]=x,x=fa[x];
        while (dis[x]<dis[y]) w[++w[0]]=y,y=fa[y];
        while (x!=y)
        {
            e[++e[0]]=x,x=fa[x];
            w[++w[0]]=y,y=fa[y];
        }
        fo(j,1,w[0]/2) swap(w[j],w[w[0]-j+1]);
        fo(j,1,e[0]+1) h[j]=0;
        fd(j,e[0],1) h[j]=max(h[j+1],a[fa[e[j]]]);
        H=mxf=g[0]=-mx;
        fo(j,1,e[0]) g[j]=max(g[j-1],f[e[j]]-h[j]);
        r=e[0];
        fo(j,1,w[0])
        {
            H=max(H,a[fa[w[j]]]);
            while (r&&H>h[r])
            {
                mxf=max(mxf,f[e[r]]);
                r--;
            }
            f[w[j]]=max(mxf-H,g[r])+a[w[j]];
        }
    }
    x=d[d[0]];
    while(x) ans=max(f[x],ans),x=fa[x];
    printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值