区间DP
以区间长度作为阶段,区间左右端点作为状态的一类DP,其子问题与原问题构成像线段树一样的结构
【石子合并】 这太经典了...不想再做一遍了
【Polygon】倍增一倍做石子合并
【Exploring Pyramids】
将树上的子树与字符串中的区间建立联系
阶段:区间长度
状态:区间左右端点
决策:选取子树所占区间利用分类加法和分步乘法原理统计答案
树形DP
在树上做dp,通常以树的深度作为阶段
【没有上司的舞会】也一样经典
【选课】树上背包与虚拟源点
【Accumulation Degree】
Description
给定一棵无根树,每条边有一个流量上限,指定一点为源点,有无限水,
树中除该点外其他度数为1的点为汇点,整个水系的流量就定义为流过每个汇点的最大流量之和
求以哪个点为源点时整个水系的流量最大,输出这个最大值。
点数<=2e5
Sol
朴素做法:以任意一点为根做一次dp总计答案,复杂度O(n2)
发现在朴素做法中常常重复统计答案,于是考虑记忆化搜索,存d[i][j]表示j以i为父节点时的最大流量
然而往下看发现lyd大佬的nb方法只用搜两次。。。
- 二次扫描与换根法
1 . 第一次扫描时,任选一个点为根,在“有根树”上执行一次树形DP,也就是在
回溯时发生的、自底向上的状态转移
2 . 第二次扫描时,从刚才选出的根出发,对整棵树执行一次深度优先遍历,在每次
递归前进行自顶向下的推导,计算出“换根”后的解
(摘自算法竞赛进阶指南)
运用于这道题,就是以1为根DP一次算出每个节点最大流量
再从1往下深搜一遍,每到一个点时令它为根,则答案为它的“父亲”那棵子树的贡献(搜索时算出并往下带)
加上之前算出的它的流量
Code
#include#include #include #include #define ll long long using namespace std; const int N=2e5+10; struct node { int v; ll w; }; vector link[N]; int n,T,u,v; ll w,f[N],ans; void dfs1(int u,int fa,ll w) { f[u]=0; int size=link[u].size(); bool flag=false; for(int i=0;i ) { node t=link[u][i]; if(t.v==fa) continue; dfs1(t.v,u,t.w),flag=true; f[u]+=min(t.w,f[t.v]); } if(!flag) f[u]=w; } void dfs2(int u,int fa,ll res) { ans=max(ans,f[u]+res); int size=link[u].size(); for(int i=0;i ) { node t=link[u][i]; if(t.v==fa) continue; dfs2(t.v,u,min(t.w,res+f[u]-min(t.w,f[t.v]))); } } int main() { scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=1;i<=n;i++) link[i].clear(); for(int i=1;i ) { scanf("%d%d%lld",&u,&v,&w); link[u].push_back((node){v,w}); link[v].push_back((node){u,w}); } dfs1(1,0,1<<30); ans=0; dfs2(1,0,0); printf("%lld\n",ans); } return 0; }