Paths 升级(DFS序+线段树+LCA+树形DP)

2015多校1 Tree chain problem

思路

之前那道Paths的升级版
在每一条链上加上了一定的权值
然后求最大值

其实就加了一个权值的条件
但这样一来题目基本上是完全不一样了
用贪心显然不行了

在树上求最大值可以试一下树形DP
定义dp[i]表示i的子树的最大值
如果要选用一条链更新一个根节点的信息
我们需要知道加上这条链的影响
首先加上这条链的权值以及这条链上点的dp值(假设这些dp值已经更新了)

定义sum[i]表示i的子节点的子树的dp值的和
然后就可以加上这条链上的sum值
但直接这样加我们会发现会将在这条链上除LCA外的点的dp值算进去
那么再减掉就好了
也就是:val + ∑sum[k] - ∑dp[k](k为链上的点)
注:LCA的DP值为0,不用直接加回去
这样就可以更新dp值了

但这样显然超时
得想办法把这个∑求和给消掉
再短时间内求出整条链的状态和

让我们回到一个简单点的问题:
如何求一条链上的权值和
其实这样的查询可以再O(1)的时间做到
首先预处理出根节点到所有点的路上的权值的和
再通过:根分别到两个节点的权值的和-根到LCA的值+LCA的值
就可以直接计算了

再到当前的问题
因为我们的树形DP是从深到浅的
所以当前节点以及当前节点到根的所有节点都是没有值的
所以只需要“根分别到两个节点的权值的和”这一步
注意:再向上更新的过程中,路径上的点的权值会改变
所以需要不断维护当前点的子树上的所有点
于是我们需要一种能在短时间内查询和区间更新的数据结构
那么。。线段树(或是树状数组)

直接用线段树显然不行,一棵子树上的点不一定连续
那么再引入DFS序,记录一段子树的区间
这样就可以用线段树查询了

使用线段树后,上面的状态转移也要发生变化
因为在更新的时候,当前的sum值是还未更新的,所以手动加上
dp[i]=max(sum[i],sum[i]+val+线段树查询的sum的和-线段树查询的dp的和)
注意:更新一个dp值后要更新线段树

题解

#include
#include
#include
#include
#include
using namespace std;

#define FOR(i,a,b) for(int i=(a),i##_end_=(b);i<=i##_end_;++i)
#define DOR(i,a,b) for(int i=(a),i##_end_=(b);i>=i##_end_;--i)
#define INF 0x3f3f3f3f
#define M 100005
#define N 20

void chkmx(int &a,int b) {if(avoid chkmi(int &a,int b) {if(a>b)a=b;}
int MAX(int a,int b) {return a>b?a:b;}
int MIN(int a,int b) {return astruct edge {int fr,to,v;};
vectorG[M];
vector<int> es[M];
int n,m,T,a,b,c;
int dep[M],Fa[M][N],Lt[M],Rt[M];
int dp[M],sum[M];

struct node {
    int dp,sum;
    node operator +(const node &a)const {
        node tmp;
        tmp.dp=dp+a.dp;
        tmp.sum=sum+a.sum;
        return tmp;
    }
};
struct Get_Dfs {
    void dfs(int s,int fr) {
        Lt[s]=++T;
        dep[s]=dep[fr]+1,Fa[s][0]=fr;
        FOR(i,0,es[s].size()-1) {
            int y=es[s][i];
            if(y==fr) continue;
            dfs(y,s);
        }
        Rt[s]=T;
    }
    void solve() {
        T=0;
        dfs(1,0);
    }
} Get_Dfs;
struct LCA {
    void Init() {
        FOR(j,1,N-1)FOR(i,1,n)
        Fa[i][j]=Fa[Fa[i][j-1]][j-1];
    }
    void up(int &s,int step) {
        FOR(i,0,N-1) {
            if((1<int solve(int a,int b) {
        if(dep[a]>dep[b]) swap(a,b);
        up(b,dep[b]-dep[a]);
        if(a==b) return a;
        DOR(i,N-1,0) {
            if(Fa[a][i]!=Fa[b][i])
                a=Fa[a][i],b=Fa[b][i];
        }
        return Fa[a][0];
    }
}LCA;
struct Segment_Tree {
    struct W {
        int L,R;
        node x;
    } tree[M<<2];
    void build(int L,int R,int p) {
        tree[p].L=L,tree[p].R=R;
        tree[p].x.dp=0;
        tree[p].x.sum=0;
        if(L==R) return ;
        int mid=L+R>>1;
        build(L,mid,p<<1);
        build(mid+1,R,p<<1|1);
    }
    node query(int x,int p) {
        int L=tree[p].L,R=tree[p].R;
        if(L==R) return tree[p].x;
        int mid=L+R>>1;
        if(x<=mid) return tree[p].x+query(x,p<<1);
        else return tree[p].x+query(x,p<<1|1);
    }
    void update(int L,int R,node a,int p) {
        if(L==tree[p].L&&R==tree[p].R) {
            tree[p].x=tree[p].x+a;
            return ;
        }
        int mid=tree[p].L+tree[p].R>>1;
        if(R<=mid) update(L,R,a,p<<1);
        else if(L>mid) update(L,R,a,p<<1|1);
        else update(L,mid,a,p<<1),update(mid+1,R,a,p<<1|1);
    }
}Tree;
struct Tree_DP{
    void Init(){
        FOR(i,1,m) {
            scanf("%d%d%d",&a,&b,&c);
            G[LCA.solve(a,b)].push_back((edge){a,b,c});
        }
    }
    void dfs(int s,int fr) {
        FOR(i,0,es[s].size()-1) {
            int y=es[s][i];
            if(y==fr)continue;
            dfs(y,s);
            sum[s]+=dp[y];
        }
        dp[s]=sum[s];
        FOR(i,0,G[s].size()-1) {
            int fr=G[s][i].fr,to=G[s][i].to;
            node a=Tree.query(Lt[fr],1);
            node b=Tree.query(Lt[to],1);
            chkmx(dp[s],sum[s]+G[s][i].v+a.sum+b.sum-a.dp-b.dp);
        }
        Tree.update(Lt[s],Rt[s],(node){dp[s],sum[s]},1);
    }
    void solve(){
        Init();
        dfs(1,0);
    }
}Tree_DP;
struct Solve {
    void Input() {
        cin>>n>>m;
        FOR(i,2,n) {
            scanf("%d %d",&a,&b);
            es[a].push_back(b);
            es[b].push_back(a);
        }
    }
    void Work() {
        Get_Dfs.solve();
        LCA.Init();
        Tree.build(1,n,1);
        Tree_DP.solve();
    }
    void Print(){
        printf("%d\n",dp[1]);
    }
    void Clear() {
        memset(dp,0,sizeof(dp));
        memset(sum,0,sizeof(sum));
        FOR(i,1,n) es[i].clear();
        FOR(i,1,n) G[i].clear();
    }
} Solve;
int main() {
    int q;
    cin>>q;
    while(q--) {
        Solve.Input();
        Solve.Work();
        Solve.Print();
        Solve.Clear();
    }
    return 0;
}

你可能感兴趣的:(DP,——树形DP,图论,数据结构,——LCA,——线段树,DFS序)