2019 杭电HDU多校第八场 HDU6662 Acesrc and Travel (换根dp)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6662

题目大意:
一颗树的n个节点,A和B两个人每次选一个节点,在一个人选了第i个节点后,两个人分别获得贡献为 ai 和 bi。
每次选的节点必须与上一个人选的节点相连,且一个点只能被选一次,每个人的目的为使自己获得的总贡献减去对方的值最大。
保证每个人都选最优解,A先选,问A能获得的最大差值是多少

思路:
刚做过一道类似的题,CF731E,同样为两个人取值使差值最大,故用相同思路
即将dp数组设计为二维,一维表示位置,第二维0/1,分别表示该点由A选时A与B的最大差值 和 该点由B选时B与A的最大差值
由于选定一个点后,对方有可能向父节点方向选,也有可能向子节点方向
故用换根dp,第一次dfs求任意一个节点作为起点的答案,第二次dfs求出每个节点作为起点的答案。
转移思路为当A选了一个节点,那么下一个节点由B选时一定会使B-A的差值尽可能大。
因此转移时倒着考虑,可得每次由最小值转移来。
方程可得
dp[u][1] = min( a[u] - b[u] - dp[v][0] )
dp[u][0] = min( b[u] - a[u] - dp[v][1] )

在第二遍dfs时,对于每个节点可能从父节点转移,也可能从子节点转移。
而在由父节点转移时需要先将父节点由该节点转移来的结果去掉,防止结果出现先从子节点转移到父节点再转移回子节点。
故需要维护一个前缀最小值和一个后缀最小值,也可对每个点维护最小值与次小值来完成。
因为第二种方法代码量较大,此处便采取第一种。
(此处思路参考了其他博客QAQ)

代码见下

#include <bits/stdc++.h>
using namespace std;

typedef pair<long long,long long> P;

long long dp[100005][2],a[100005],b[100005];
vector<int> E[100005];
vector<P> pre[100005];
long long inf=0x3f3f3f3f3f3f3f3f;
long long res=-inf;

void dfs1(int x,int fa){
    dp[x][0]=inf;
    dp[x][1]=inf;
    for(auto i:E[x]){
        if(i==fa) continue;
        dfs1(i,x);
        dp[x][0]=min(dp[x][0],b[x]-a[x]-dp[i][1]);
        dp[x][1]=min(dp[x][1],a[x]-b[x]-dp[i][0]);
    }
    if(dp[x][0]==inf)
        dp[x][0]=b[x]-a[x],
        dp[x][1]=a[x]-b[x];
}

void dfs2(int x,int fa){
    dp[x][0]=inf;
    dp[x][1]=inf;
    for(auto i:E[x]){
        dp[x][0]=min(dp[x][0],b[x]-a[x]-dp[i][1]);
        dp[x][1]=min(dp[x][1],a[x]-b[x]-dp[i][0]);
        pre[x].push_back(P(dp[x][0],dp[x][1]));
    }
    long long t0=inf,t1=inf;
    for(int i=E[x].size()-1;i>=0;i--){
        int v=E[x][i];
        dp[x][0]=t0;
        dp[x][1]=t1;
        if(i>0){
            dp[x][0]=min(dp[x][0],pre[x][i-1].first);
            dp[x][1]=min(dp[x][1],pre[x][i-1].second);
        }
        if(dp[x][0]==inf)
            dp[x][0]=b[x]-a[x],dp[x][1]=a[x]-b[x];
        t0=min(t0,b[x]-a[x]-dp[v][1]);
        t1=min(t1,a[x]-b[x]-dp[v][0]);
        if(v!=fa)
            dfs2(v,x);
    }
    //cout<<x<<" "<<dp[x][0]<<" "<<t0<<endl;
    dp[x][0]=t0;
    dp[x][1]=t1;
}


int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        res=-inf;
        for(int i=1;i<=n;i++) E[i].clear();
        for(int i=1;i<=n;i++) pre[i].clear();
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<=n;i++) cin>>b[i];
        for(int i=1;i<n;i++){
            int x,y;
            cin>>x>>y;
            E[x].push_back(y);
            E[y].push_back(x);
        }
        dfs1(1,1);
        dfs2(1,1);
        for(int i=1;i<=n;i++) res=max(res,dp[i][1]);
        cout<<res<<endl;
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值