[提升性选讲] 树形DP进阶:一类非线性的树形DP问题(例题 BZOJ4403 BZOJ3167)
转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7337179.html
树形DP是一种在树上进行的DP相对比较难的DP题型.由于状态的定义多种多样,因此解法也五花八门,经常成为高水平考试的考点之一.
在树形DP的问题中,有这样一类问题:其数据范围相对较小,并且状态转移一般与两两节点之间的某些关系有关。
今天,我们就来研究一下这类型的问题,并且总结一种(相对套路的)解决大多数类型题的思路。
首先,我们用一道相对简单的例题来初步了解这个类型题的大致思路,以及一些基本的代码实现。
BZOJ 4033: [HAOI2015]树上染色
Time Limit: 10 Sec Memory Limit: 256 MB
Description
Input
Output
Sample Input
1 2 3
1 5 1
2 3 1
2 4 2
Sample Output
【样例解释】
将点1,2染黑就能获得最大收益。
状态确立
首先,我们可以一眼看出,只用诸如"处理完以i为根的子树的最大收益"等一维的状态不能处理这个问题,
这个时候,我们可以考虑加一维来表示更多的限制条件:设f[i][j]表示"在以i为根的子树中染j个黑色点的最大收益",最终答案即是f[1][k]
状态转移
其实状态定义蛮好想,但是,怎么状态转移呢?
由于......数据范围很小,而我们权值的计算又与两两点之间关系有关,因此我们可以考虑枚举点对的暴力做法.
我们考虑,对于每个点对来说,他们之间的贡献只会在他们的LCA处贡献O(1)的时间复杂度.
由于一共只有n2数量级的点对,因此我们如果这样做的话算法复杂度是O(n2)的.
既然这种算法的复杂度是O(n2)的,我们就可以随便转移考虑一种暴力的转移:
枚举当前考虑的子树中有几个黑点,并考虑合并子树带来的贡献.
我们考虑,如果我们只统计当前子树内的贡献,显然是不好转移的,因为无法考虑与子树外面点的关系
所以,我们把子树外面的点与子树内点的贡献也统计在f数组里面,也就是说"外面伸进来的边"也被统计了进来
这样,由于子树内可以被统计的边的贡献已经被全部统计完,我们就可以通过考虑当前合并的两节点之间的这条边来统计贡献:
在上图中,子树里面红色边的贡献以及考虑完,现在我们更新的是子树外面的点与子树内的点通过蓝色边贡献的权值.
设节点rt的子树大小为size[rt],rt原来染色了j个黑点,设节点u的子树大小为size[u],u原来染色了v个黑点,设边权为val
经过图中的蓝边这条边,u里边的黑点与外面的黑点产生了v*(k-v)个黑点对.
同理,里边的白点与外面的白点产生了(size[u]-v])*(n-k-(size[u]-v))个白点对
那么rt->u这条边总共产生了(v*(k-v)+(size[u]-v])*(n-k-(size[u]-v)))*val的新的贡献.
这样我们就统计出来了新的贡献,现在以rt为根的子树总贡献是f[rt][j]+f[u][v]+(v*(k-v)+(size[u]-v])*(n-k-(size[u]-v)))*val
我们用上面这个式子去更新f[rt][j+v]的答案即可.
代码实现
在代码中,这个算法是O(n2)就变得显而易见了.先给出dp过程的代码,我们开始分析:
void dp(int rt,int fa)
{
register int i,j,k,u;
LL cnt;size[rt]=;
for(i=adj[rt];i;i=s[i].next)
{
u=s[i].zhong;
if(u!=fa)
{
dp(u,rt);
memset(g,,sizeof(g));
for(j=;j<=size[rt];++j)
for(k=;k<=size[u];++k)
cnt=k*1ll*(m-k)+(size[u]-k)*1ll*(n-m-(size[u]-k)),
g[j+k]=max(g[j+k],f[rt][j]+f[u][k]+cnt*s[i].val);
size[rt]+=size[u];
for(j=;j<=size[rt];++j)f[rt][j]=g[j];
}
}
}
就像上面说的,我们考虑把u这棵子树合并到rt里面产生的新贡献.
值得注意的一点是,我们如果先不合并起来,用刷表法去更新,要比先合并起来用填表法更新快不少.
这一点带来的优化很明显,因为合并后j循环的次数变多了.
具体的效率差别...大概是这样(上面那个提交是后合并的打法):
现在,这道题基本就我们解决了.完整代码见下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N=;
int n,m,e,adj[N],size[N];
LL f[N][N],g[N];
struct edge{int zhong,next;LL val;}s[N<<];
inline void add(int qi,int zhong,LL val)
{s[++e].zhong=zhong,s[e].val=val,s[e].next=adj[qi],adj[qi]=e;}
void dp(int rt,int fa)
{
register int i,j,k,u;
LL cnt;size[rt]=;
for(i=adj[rt];i;i=s[i].next)
{
u=s[i].zhong;
if(u!=fa)
{
dp(u,rt);
memset(g,,sizeof(g));
for(j=;j<=size[rt];++j)
for(k=;k<=size[u];++k)
cnt=k*1ll*(m-k)+(size[u]-k)*1ll*(n-m-(size[u]-k)),
g[j+k]=max(g[j+k],f[rt][j]+f[u][k]+cnt*s[i].val);
size[rt]+=size[u];
for(j=;j<=size[rt];++j)f[rt][j]=g[j];
}
}
}
int main()
{
scanf("%d%d",&n,&m);
register int i,a,b;LL c;
for(i=;i<n;++i)
scanf("%d%d%lld",&a,&b,&c),add(a,b,c),add(b,a,c);
dp(,),printf("%lld\n",f[][m]);
}
上面这道题还算一道比较简单的树形DP.这道题最大的特点就是那个非线性的O(n2)过程了.
这类非线性的DP一般状态定义和状态转移都比较复杂,但是主要的思想要点是"合并".
如果你发现某个树归问题是与两点间关系有关,那他很可能就是一个这种类型的DP
下面,我们再来看一道题.这道题可就没有上题那么简单了......
BZOJ 3167: [Heoi2013]Sao
Time Limit: 30 Sec Memory Limit: 256 MB
Description
Input
Output
对于每个数据,输出一行一个整数,为攻克关卡的顺序方案个数,mod1,000,000,007输出。
Sample Input
10
5 > 8
5 > 6
0 < 1
9 < 4
2 > 5
5 < 9
8 < 1
9 > 3
1 < 7
10
6 > 7
2 > 0
9 < 0
5 > 9
7 > 0
0 > 3
7 < 8
1 < 2
0 < 4
10
2 < 0
1 > 4
0 > 5
9 < 0
9 > 3
1 < 2
4 > 6
9 < 8
7 > 1
10
0 > 9
5 > 6
3 > 6
8 < 7
8 > 4
0 > 6
8 > 5
8 < 2
1 > 8
10
8 < 3
8 < 4
1 > 3
1 < 9
3 < 7
2 < 8
5 > 2
5 < 6
0 < 9
Sample Output
3960
1834
5208
3336
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int mod=,N=;
int n,adj[N],e;
LL g[N],C[N][N],sum[N][N],size[N],f[N][N];//以i为根的子树,有j个比i小(在i之前访问)的方案数
struct edge{int zhong,next,val;}s[N<<];
inline void add(int qi,int zhong,int val)
{s[++e].zhong=zhong;s[e].val=val;s[e].next=adj[qi];adj[qi]=e;}
void dfs(int rt,int fa)
{
size[rt]=f[rt][]=;
for(int i=adj[rt];i;i=s[i].next)
{
int u=s[i].zhong;
if(u!=fa)
{
dfs(u,rt);int limit=size[rt]+size[u];
for(int i=;i<limit;i++)g[i]=;
if(s[i].val==)//rt比u小
for(int j=;j<size[rt];j++)//已经合并完成的以rt为根节点的子树中有j个比rt大(在rt之前访问)
for(int k=;k<=size[u];k++)//以u为根节点的子树中有k个比rt大(在rt之后访问)
{
LL tmp1=f[rt][size[rt]-j-]/*比rt小的size[rt]-j-1的合法方案数*/%mod*(sum[u][size[u]-]-sum[u][size[u]-k-]+mod)%mod;
//u里面有k个比rt大的,不一定有几个比u大
LL tmp2=C[j+k][k]*C[limit-j-k-][size[u]-k]%mod;
//组合数看方案数,前者表示在新的j+k个比rt大的数中新插入的k个数所在的位置
//后者表示比rt小的size-j-k-1个数中u剩下的size[u]-k的排列
g[limit-j-k-]=(g[limit-j-k-]+tmp1*tmp2%mod)%mod;
//此时有limit-j-k-1个数比rt小,更新答案
}
else//rt比u大(在u之后访问)
for(int j=;j<size[rt];j++)//以rt为根节点的子树中有j个比rt小
for(int k=;k<=size[u];k++)//以u为根节点的子树中有k个比rt小
{
LL tmp1=f[rt][j]%mod*sum[u][k-]%mod;
//u里面有k个比rt小的,不一定几个比u小
LL tmp2=C[j+k][k]*C[limit-j-k-][size[u]-k]%mod;//和上面组合数的统计类似.
g[j+k]=(g[j+k]+tmp1*tmp2%mod)%mod;
}
size[rt]+=size[u];//不断合并每棵子树
for(int j=;j<size[rt];j++)f[rt][j]=g[j];//更新f数组
}
}
sum[rt][]=f[rt][];
for(int j=;j<size[rt];j++)sum[rt][j]=(sum[rt][j-]+f[rt][j])%mod;//全部合并完成,计算合法方案前缀和
}
int main()
{
for(int i=;i<=;i++)
{
C[i][]=;
for(int j=;j<=i;j++)
C[i][j]=(C[i-][j]+C[i-][j-])%mod;
}
int t,a,b;char c[];scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
memset(size,,sizeof(size));
memset(f,,sizeof(f));
memset(sum,,sizeof(sum));
e=;memset(adj,,sizeof(adj));
for(int i=;i<n;i++)
{
scanf("%d%s%d",&a,c,&b),a++,b++;
if(c[]=='>')add(b,a,),add(a,b,-);
else add(a,b,),add(b,a,-);
}
dfs(,);int ans=;
for(int i=;i<n;i++)
ans=(ans+f[][i])%mod;
printf("%d\n",ans);
}
}
非线性的树形DP是一类很考验DP思维,尤其是DP状态定义能力的问题,这就需要OIer们通过刷题来不断积累做题经验了(其实什么类型题不是呢).希望大家能从我的博文中有所收获:)
[提升性选讲] 树形DP进阶:一类非线性的树形DP问题(例题 BZOJ4403 BZOJ3167)的更多相关文章
- [胡泽聪 趣题选讲]大包子环绕宝藏-[状压dp]
Description 你有一个长方形的地图,每一个格子要么是一个障碍物,要么是一个有一定价值的宝藏,要么是一个炸弹,或者是一块空地.你的初始位置已经给出.你每次可以走到上.下.左.右这四个相邻的格子 ...
- [入门向选讲] 插头DP:从零概念到入门 (例题:HDU1693 COGS1283 BZOJ2310 BZOJ2331)
转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7326874.html 最近搞了一下插头DP的基础知识……这真的是一种很锻炼人的题型…… 每一道题的状态都不一样 ...
- DP选讲
$DP$选讲直接上题吧放个题单[各省省选DP](https://www.luogu.com.cn/training/151079)$P5322[BJOI2019]$排兵布阵一眼题,考虑$dp[i][j ...
- PJ可能会用到的动态规划选讲-学习笔记
PJ可能会用到的动态规划选讲-学习笔记 by Pleiades_Antares 难度和速度全部都是按照普及组来定的咯 数位状压啥就先不讲了 这里主要提到的都是比较简单的DP 一道思维数学巧题(补昨天) ...
- 斯坦福NLP课程 | 第2讲 - 词向量进阶
作者:韩信子@ShowMeAI,路遥@ShowMeAI,奇异果@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www. ...
- hs-black 杂题选讲
[POI2011]OKR-Periodicity 考虑递归地构造,设 \(\text{solve(s)}\) 表示字典序最小的,\(\text{border}\) 集合和 \(S\) 的 \(\tex ...
- PJ考试可能会用到的数学思维题选讲-自学教程-自学笔记
PJ考试可能会用到的数学思维题选讲 by Pleiades_Antares 是学弟学妹的讲义--然后一部分题目是我弄的一部分来源于洛谷用户@ 普及组的一些数学思维题,所以可能有点菜咯别怪我 OI中的数 ...
- 正睿OI DAY3 杂题选讲
正睿OI DAY3 杂题选讲 CodeChef MSTONES n个点,可以构造7条直线使得每个点都在直线上,找到一条直线使得上面的点最多 随机化算法,check到答案的概率为\(1/49\) \(n ...
- 【ZJ选讲·BZOJ 5073】
小A的咒语 给出两个字符串A,B (len<=105) 现在可以把A串拆为任意段,然后取出不超过 x 段,按在A串中的前后顺序拼接起来 问是否可以拼出B串. [题解] ①如果遇 ...
随机推荐
- Nginx应用场景
1. Nginx应用场景 1)http服务器.Nginx可以独立的提供http服务,可以做网页静态服务器(也就是将静态文件放到nginx目录下,通过nginx来访问就ok) 2)虚拟主机,可以在一 ...
- 怎么使用Spring配置事务 ?
Spring同时支持编程式事务策略和声明式事务策略,大部分时候都采用声明式事务策略. 声明式事务管理的配置方式,通常有以下4种: (1) 使用TransactionProxyFactoryBean为目 ...
- AndroidStudio更改包名
最近开发一个项目 和以前开发的某一个功能类似 不想再重新搭建界面 从零开始去写... 就想把原来的项目copy一份 但是这样的话安装在手机中会把原来的项目覆盖掉 这是因为它们的applicationI ...
- Java编辑PPT的柱状图,与内嵌的Excel联动
/** * 条形图:柱形图 的数据写入方法 * @param slide 图表 * @param index 柱状图的下标 * @param data 要填充的数据 * @param titles 内 ...
- spring cloud 入门系列:总结
从我第一次接触Spring Cloud到现在已经有3个多月了,当时是在博客园里面注册了账号,并且看到很多文章都在谈论微服务,因此我就去了解了下,最终决定开始学习Spring Cloud.我在一款阅读A ...
- 关于springcloud的一些问题总结.txt
@Bean public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource source = new UrlBasedCo ...
- PHP核心技术——接口
接口: 接口这样描述自己:对于实现我的所有类,看起来都应该像我现在这个样子 接口含义:采用一个特定接口的所有代码都知道对于那个接口会调用什么方法. interface mobile{ public f ...
- SICP读书笔记 3.4
SICP CONCLUSION 让我们举起杯,祝福那些将他们的思想镶嵌在重重括号之间的Lisp程序员 ! 祝我能够突破层层代码,找到住在里计算机的神灵! 目录 1. 构造过程抽象 2. 构造数据抽象 ...
- Flink HA
standalone 模式的高可用 部署 flink 使用zookeeper协调多个运行的jobmanager,所以要启用flink HA 你需要把高可用模式设置成zookeeper,配置zookee ...
- DDMS_Threads的简单使用
title: DDMS_Threads的简单使用 date: 2016-07-20 00:44:35 tags: [DDMS] categories: [Tool,IDE] --- 概述 本文记录在 ...