• 提米树/【BZOJ2031】剪枝-DP


    【问题描述】

    Temmie 村里有很多提米树,上面长满了 Temmie 薄片。一天,Flowey 来到
    Temmie 村做客,Temmie 们想要送给它一棵提米树。
    对于这棵提米树,满足这样的性质:
    ·1.这棵树可以看做一棵 n 个点的无向无环图,每个点上长了一定数量的
    Temmie 薄片,薄片数量记为这个点的权值,这些点被标记为 1 到 n 的整数,其
    中 1 号点是树的根,没有孩子的点是树上的叶子。
    ·2.对于这棵树进行深度优先搜索,每个点从左到右按顺序遍历每个孩子,得
    到这棵树的 DFS 序。
    ·3.定义(a,b)是一对相邻的叶子,当且仅当没有其它的叶子节点在 DFS 序上在
    a,b 之间。每对相邻的叶子都会产生一个代价,代价为 a 到 b 路径上(不包含 a,b)
    的点中,最大点权值。
    ·4.提米树可以提供决心,一棵提米树能提供的决心的数量是树上所有叶子上
    长的 Temmie 薄片数量和,减去所有相邻叶子的代价。
    Flowey 虽然不是以 Temmie 薄片为食的生物,但是它需要决心。这棵提米树
    能提供的决心太少了,Temmie 们决定对这棵树进行若干次剪枝(可以不剪枝),使
    得这棵树能提供的决心最多。
    一次剪枝定义为:如果一个点的孩子都是叶子,就可以把它所有的孩子剪掉。

    【输入格式】

    从 temmie.in 中读入数据。
    输入数据的第一行为一个正整数 n,代表提米树上点的数量。
    接下来 n 行,对于每行:
    第一个数为一个正整数,代表这个点上 Temmie 薄片的数量
    第二个数为一个整数 num,代表这个点孩子的数量
    接下来 num 个数,依次表示这个点从左到右的孩子。

    【输出格式】

    输出到 temmie.out 中
    输出包含一个数,为这棵树剪枝后最多能提供的决心的数量。

    【样例输入 1】

    3
    1 2 2 3
    2 0
    2 0

    【样例输出 1】

    3

    【样例输入 2】

    5
    1 2 2 3
    2 1 4
    3 1 5
    4 0
    5 0

    【样例输出 2】

    6


    思路

    • 关于这道题的两个关键性质/?:
    • 1.注意到这里求的都是每两个相邻叶子节点路径上的极值,用倍增求复杂度是nlogn,但用暴力dfs求刚好遍历整棵树复杂度O(n);
    • 2.无论如何剪枝,两个剪枝前不相邻的叶子都不会在剪枝后相邻【关键!!!】
    • dp[i]表示到i节点的答案,初步写出dp方程:dp[x]=dp[y]-max(h[x],H[y])+num[x],其中h[x]为x至lca的最大值,H[y]同理
    • 发现此dp方程枚举每一对h[x],H[y],时间复杂度为n^2(不是/然而并不会算...)超时100%,想到优化时间
    • 想到前缀优化,与前缀和类似,前缀最大值(然而并不能想到)
    • 又应为前缀最大值后h数组单调递增,想到双指针[套路]将复杂度降到O(n)
    • 思路看起来很复杂,但实现稍简单,具体注释见代码

    代码

    #include <iostream>
    #include <cstdio>
    #define inf 0x7f7f7f7f
    #define maxn 100005
    using namespace std;
    int n,fa[maxn],head[maxn],cnt,keep[maxn],deep[maxn],w[maxn],e[maxn],h[maxn],H,num[maxn],dp[maxn],g[maxn],tmax;
    struct fdfdfd{int next,to;}a[maxn];
    void addedge(int x,int y){a[++cnt].to=y; a[cnt].next=head[x]; head[x]=cnt;}
    int max(int a,int b){return a>b?a:b;}
    void dfs(int x)
    {
    	deep[x]=deep[fa[x]]+1;
    	if(!head[x]) keep[++cnt]=x;
    	else for(int i=head[x];i;i=a[i].next) dfs(a[i].to);
    }
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1,tnum;i<=n;++i)
    	{
    		scanf("%d%d",&num[i],&tnum);
    		for(int j=1,ch;j<=tnum;++j) scanf("%d",&ch),addedge(i,ch),fa[ch]=i;
    	}
    	cnt=0; dfs(1);//一遍dfs存储树的所有叶子节点
    	int x=keep[1],y;
    	while(x) dp[x]=num[x],x=fa[x];//初始化第一条链的值,dp[x]=num[x];
    	for(int i=2;i<=cnt;++i)
    	{
    		w[0]=e[0]=0; x=keep[i]; y=keep[i-1];//w,e暴力爬树分别记录想x,y到lca路径上的点
    		while(deep[x]>deep[y]) w[++w[0]]=x,x=fa[x];
    		while(deep[y]>deep[x]) e[++e[0]]=y,y=fa[y];
    		while(x!=y)
    		{
    			w[++w[0]]=x,x=fa[x];
    			e[++e[0]]=y,y=fa[y];
    		}//注意从w[0]~1记录从lca到叶节点,e同理
    		g[0]=h[e[0]+1]=H=tmax=-inf; int r=e[0];
    		for(int j=e[0];j;--j) h[j]=max(h[j+1],num[fa[e[j]]]);//前缀最大值[补充1]
    		for(int j=1;j<=e[0];++j) g[j]=max(g[j-1],dp[e[j]]-h[j]);//此处g为for中dp转移的一部分,不懂先跳过
    		for(int j=w[0];j;--j)
    		{
    			H=max(H,num[fa[w[j]]]);//省去H数组,每次直接求
    			while(r&&H>h[r]) tmax=max(tmax,dp[e[r--]]);//找到e上最后一个小于H的节点
    			dp[w[j]]=max(tmax-H,g[r])+num[w[j]];//[补充2]
    		}
    	}
    	x=keep[cnt]; int ans=-inf;
            while(x) ans=max(dp[x],ans),x=fa[x];
            printf("%d
    ",ans);
    	return 0;
    }
    //[补充1]:在阅读某些题解代码时不理解为什么时fa[e[j]],读题后发现“代价为 a 到 b 路径上(不包含 a,b)”。。。
    //[补充2]:max中,tmax-H 是h数组<H时的 dp[y]-max(h[x],H[y]) 最大值,g[r]则是h数组>H时的最大值,
    //也就同时解释为什么要提前求出g数组,而不能直接用dp[e[r]]-h[r]
    

    • 大概这道题主要的关键点在于性质2,虽然就算知道这条性质,代码的细节仍然多得令人发指,考试多半只能拿暴力
  • 相关阅读:
    CSS Modules
    回调地狱
    css实现双色饼图
    vue项目中使用less
    pug(jade) 学习笔记
    React组件proptypes, ref
    react+express实现跨域
    react高阶组件
    Oracle数据库出现锁表情况分析
    JPA常用注解记录
  • 原文地址:https://www.cnblogs.com/wuwendongxi/p/13618972.html
Copyright © 2020-2023  润新知