Flash by sshockwave [树dp]

Posted dedicatus545

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flash by sshockwave [树dp]相关的知识,希望对你有一定的参考价值。

题目

给定一棵树,每个点有一个活动时间,长度为正整数$t_i$

你需要安排每个点的活动时间什么时候开始什么时候结束,并且满足:任何一个时刻没有两个相邻的点都在活动

开始时刻为0,在以上条件下最小化所有点的结束时间之和

$n leq 2000$

思路

首先,给定的所有$t_i$都是正整数,说明答案一定是整数(这虽然很显然,但是很重要)

考虑某一个点什么时候开始

显然,最优的情况下它的开始时间可以被安排到和自己某个相邻点的结束时间相邻(或者它自己是在开始时刻就开始的)

又考虑到隔壁点也满足这一条,所以对于这个点来说,它的前驱时间(也就是在开始前的等待时间)一定可以被表示为从这个点开始的一条树链的时间和

注意上面的“可以被表示为”这样的表述:不是说只能这么安排,是说这样安排是可行的,而且方便我们设计算法

对于每个点$u$,计算数组$dis[u]$,表示排序去重后的从$u$出发的所有树链的长度

然后设$dp[u][i]$表示从根节点开始$dp$,在当前节点的子树中得到的结果:$dp[u][i]$表示在$u$开始之前已经经过了$dis[u][i]$这么长的等待时间

注意这里的$dp$并没有考虑父亲那边,而是只考虑了子树部分

因此在递归回到父亲那里做转移的时候,要这样操作:


        t1[0]=t2[cnt[v]+1]=1e15;
        for(i=1;i<=cnt[v];i++) t1[i]=min(t1[i-1],dp[v][i]);
        for(i=cnt[v];i>=1;i--) t2[i]=min(t2[i+1],dp[v][i]);
        l=0;r=0;
        for(i=1;i<=cnt[u];i++){
            while(dis[v][l+1]+w[v]<=dis[u][i]) l++;
            while(dis[v][r]<dis[u][i]+w[u]) r++;
            dp[u][i]+=min(t1[l],t2[r]);
        }

做一个前后缀最小值,然后两边双指针进去搞到范围,然后再转移

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#define ll long long
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int n,first[10010],cnte=-1;
struct edge{
    int to,next;
}a[20010];int w[10010];
inline void add(int u,int v){
    a[++cnte]=(edge){v,first[u]};first[u]=cnte;
    a[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
ll dis[2010][2010],lis[2010],dp[2010][2010];
int root,cnt[2010];
void getlis(int u,int f,ll d){
    lis[++cnt[root]]=d;
//  cout<<"getlis "<<u<<' '<<root<<' '<<d<<'
';
    int i,v;
    for(i=first[u];~i;i=a[i].next){
        v=a[i].to;if(v==f) continue;
        getlis(v,u,d+w[v]);
    }
}
void init(){
    memset(first,-1,sizeof(first));
    int i,num,t1,t2;
    n=read();
    for(i=1;i<n;i++){
        t1=read();t2=read();
        add(t1,t2);
    }
    for(i=1;i<=n;i++) w[i]=read();
    for(i=1;i<=n;i++){
        root=i;getlis(i,0,0);
        sort(lis+1,lis+cnt[i]+1);
        cnt[i]=unique(lis+1,lis+cnt[i]+1)-lis-1;
        memcpy(dis[i]+1,lis+1,sizeof(ll)*(cnt[i]+1));
        dis[i][0]=-1e15;
        dis[i][cnt[i]+1]=1e15;
    }
}
void dfs(int u,int f){
    int i,v,j,l,r;
    for(i=1;i<=cnt[u];i++) dp[u][i]=dis[u][i]+w[u];
    for(i=first[u];~i;i=a[i].next){
        v=a[i].to;if(v==f) continue;
        dfs(v,u);
    }
    for(j=first[u];~j;j=a[j].next){
        v=a[j].to;if(v==f) continue;
        static ll t1[2010],t2[2010];
        t1[0]=t2[cnt[v]+1]=1e15;
        for(i=1;i<=cnt[v];i++) t1[i]=min(t1[i-1],dp[v][i]);
        for(i=cnt[v];i>=1;i--) t2[i]=min(t2[i+1],dp[v][i]);
        l=0;r=0;
        for(i=1;i<=cnt[u];i++){
            while(dis[v][l+1]+w[v]<=dis[u][i]) l++;
            while(dis[v][r]<dis[u][i]+w[u]) r++;
            dp[u][i]+=min(t1[l],t2[r]);
        }
    }
}
int main(){
    init();
    dfs(1,0);
    int i;ll ans=1e15;
    for(i=1;i<=cnt[1];i++) ans=min(ans,dp[1][i]);
    cout<<ans<<'
';
}

以上是关于Flash by sshockwave [树dp]的主要内容,如果未能解决你的问题,请参考以下文章

Installing Ubuntu 18.04 by USB flash drive

数据结构虐哭空巢老人记

UVa 11584 Partitioning by Palindromes (简单DP)

UVA - 11584 Partitioning by Palindromes[序列DP]

POJ 2686 Traveling by Stagecoach (状压DP)

POJ2686 Traveling by Stagecoach(状压DP+SPFA)