19 hdu多校 Acesrc and Travel // 树形dp+换根

http://acm.hdu.edu.cn/showproblem.php?pid=6662

题意:

A和B博弈,给一个树,对于一个节点,其中一个人选了之后,A或者贡献\large A_i,B获得贡献\large B_i,A先选一个点,之后BA轮流选一个点(和前一个点相邻),直到无路可走为止,每个人都希望自己的贡献减去别人的贡献尽量大,求A减去B的贡献最多是多少。

思路:

先选一个点,然后把整个树拎起来,在这个状态下考虑,A如果是选了根节点,B肯定是选一个使得自己最大的儿子节点。

定义dp[i][j],j==0代表A,j==1代表B,选了i点时,获得的最大贡献。

考虑逆向得到dp[u][0]和dp[v][0]

这个时候可以得到方程  (v是u的孩子)

\large dp[u][1]=min(-dp[v][0]+a[u]-b[u])

\large dp[u][0]=min(-dp[v][1]-a[u]+b[u])

然后进行换根。

将每一个点拎起来作为新树时,最多改变u和v两个点的值,记录一下然后回溯。

因为dfs换根下去的时候,要消除要递归到的儿子对父亲的影响,所以需要记录前缀最小,逆序遍历。

有一个很caodan的地方:如果一个节点没有儿子节点,需要特殊判断一下,否则很崩!!

这题也可以通过记录第一小第二小实现。

/*   (o O o)
 *   Author : Rshs
 *   Data : 2019-08-29-20.54
 */
#include<bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const LL mod = 1e9+7;
const int MX = 1e5+5;
int n,a[MX],b[MX];
LL dp[MX][2];//up:从父亲过来
vector<int> g[MX];
vector<LL> pa[MX][2];
LL ans;
void dfs(int x,int fa){ //处理出以1为根的树
    dp[x][0]=dp[x][1]=LLONG_MAX; //注意可能没有儿子节点
    for(auto i:g[x]){
        if(i==fa) continue;
        dfs(i,x);
        dp[x][0]=min(dp[x][0],-dp[i][1]+(LL)a[x]-(LL)b[x]);
        dp[x][1]=min(dp[x][1],-dp[i][0]+(LL)b[x]-(LL)a[x]);
    }
    if(dp[x][0]==LLONG_MAX) dp[x][0]=(LL)a[x]-(LL)b[x],dp[x][1]=(LL)b[x]-(LL)a[x];
}
void DFS(int x,int fa){
    ans=max(ans,dp[x][0]);
    //前缀处理
    pa[x][0].clear();pa[x][1].clear();
    for(auto i:g[x]) pa[x][1].push_back(-dp[i][1]),pa[x][0].push_back(-dp[i][0]);
    for(int i=1;i<SZ(pa[x][0]);i++) pa[x][0][i]=min(pa[x][0][i-1],pa[x][0][i]),pa[x][1][i]=min(pa[x][1][i-1],pa[x][1][i]);
    //前缀处理
    LL suf0=LLONG_MAX,suf1=LLONG_MAX; //记录后缀
    LL aa,bb,cc,dd;//回溯的记录
    for(int i=SZ(g[x])-1;i>=0;i--){ //因为要记录后缀,逆序
        int now=g[x][i];
        aa=dp[x][0],bb=dp[x][1],cc=dp[now][0],dd=dp[now][1];
        LL tt0=suf0,tt1=suf1;
        if(i>0) tt0=min(tt0,pa[x][0][i-1]),tt1=min(tt1,pa[x][1][i-1]);
        if(tt0==LLONG_MAX) tt0=tt1=0;//如果x没有儿子节点
        dp[x][0]=tt1+(LL)a[x]-(LL)b[x];dp[x][1]=tt0-(LL)a[x]+(LL)b[x];//更新
        suf0=min(suf0,-dp[now][0]);suf1=min(suf1,-dp[now][1]); //更新后缀
        if(SZ(g[now])==1) dp[now][0]=dp[now][1]=LLONG_MAX; //x的子节点now,没有儿子,那么只能从x转移过来
        dp[now][0]=min(dp[now][0],-dp[x][1]+(LL)a[now]-(LL)b[now]);
        dp[now][1]=min(dp[now][1],-dp[x][0]-(LL)a[now]+(LL)b[now]);
        if(now!=fa) DFS(now,x); //递归
        dp[x][0]=aa,dp[x][1]=bb,dp[now][0]=cc,dp[now][1]=dd;//回溯
    }
}
void Main(){
    cin>>n;for(int i=1;i<=n;i++)scanf("%d",&a[i]);for(int i=1;i<=n;i++)scanf("%d",&b[i]);
    for(int i=1;i<=n;i++) g[i].clear();
    for(int i=1;i<n;i++){
        int vv,uu;scanf("%d%d",&uu,&vv);g[uu].push_back(vv);g[vv].push_back(uu);
    }
    if(n==1){cout<<a[1]-b[1]<<'\n';return;}
    ans=LLONG_MIN;
    dfs(1,-1);
    DFS(1,-1);
    cout<<ans<<'\n';
}
int main(){
    int ca;cin>>ca;while(ca--)Main();
    return 0;
}
/*
10
4
1 1 10 6
2 3 3 2
1 2 2 3 2 4
*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值