算法竞赛进阶指南dp学习笔记——Day2

区间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;
}

 

你可能感兴趣的:(算法竞赛进阶指南dp学习笔记——Day2)