2019 hdu 多校8 Acesrc and Travel (树形dp 换根)

链接

题意:

有两个人在树上博弈,每个点节点有两个分数a[i]和b[i],先手先选择一个点,后手在先手选的点的相邻点中选择一个点,然后先手在后手选的点的相邻点中选择一个两个人都没有走过的点,直到不能走,游戏就结束。一个人走到节点x,那么先手会获得分数a[x],后手就会会获得分数b[x]。最后询问先手能获得与后手的差值最大值。



思路:

换根的思路,先随便选择一个点作为根,算出以该点作为根的答案。然后再用一个dfs转换到以别的点为根的情况下的答案。

为了方便直接把a和b做差,每个点记录差值来代替a和b。

f ( u , p , 0 ) = max ⁡ f ( v , u , 1 ) + w u f(u,p,0) = \max f(v,u,1) + w_{u} f(u,p,0)=maxf(v,u,1)+wu
f ( u , p , 1 ) = min ⁡ f ( v , u , 0 ) + w u f(u,p,1) = \min f(v,u,0) + w_{u} f(u,p,1)=minf(v,u,0)+wu

以上两个公式是官方题解给出的,下标为0表示的是先手选择的状态,下标为1则是后手选择的状态。
不难理解,先手选择的时候会从所有相邻点后手选择的情况中选择一个最大的,然后再加上当前这个点的贡献。而后手则相反,每次从相邻点的先手选择状态中选择一个最小的,再加上当前这个点的贡献。

于是就用 d p [ x ] [ 2 ] dp[x][2] dp[x][2]来表示状态就可以了。

在换根的时候会发现如果当前的点是u,u有一个子节点v,换根的时候本质就是要把父节点当成当前节点的一个子节点,也就是说v这个点的状态需要继承父节点u的状态,当把v作为根的时候会发现,如果u的最大值或者最小值本身就是来源于v这个点的,v再去继承本来就是从自己继承出去的状态就不合理了,因此需要记录每个点的次大值和次小值,刚才的情况v就可以继承u的次值了,这个次值继承于u的其它儿子或者父亲。如果u的极值不是继承于v,那么v可以直接继承这个极值,因为极值也是继承于u的其它儿子或者父亲。

所以再开一个 d p 2 [ x ] [ 2 ] dp2[x][2] dp2[x][2]用来存放每个点的次大值或者次小值。

还有一些细节需要考虑,如就两个点的时候,记u-v,u本身就只有一个极值且继承于v,换根的的时候,v要继承就要特判为u本身的值了。这种情况可以形容为一个点只有一个儿子,树中的叶子节点都是这种情况,所有叶子节点都要特判一下。


参考代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1e18+7;
const int N=1e5+5;
ll a[N];

int head[N],cnt;
struct _edge{
    int v,nxt;
}edge[N<<2];

void addedge(int u,int v){
    edge[++cnt].v=v;
    edge[cnt].nxt=head[u];
    head[u]=cnt;
}

ll dp[N][2];
ll dp2[N][2];
ll ans=0;
bool isleaf[N];//表示一个点是否为叶子
void dfs(int u,int fa){
    bool end=true;
    for(int i=head[u];~i;i=edge[i].nxt){
        int v=edge[i].v;
        if(v==fa)continue;
        dfs(v,u);
        end=false;
        if(dp[v][1]<dp2[u][0])dp2[u][0]=dp[v][1];
        if(dp2[u][0]<dp[u][0])swap(dp[u][0],dp2[u][0]);

        if(dp[v][0]>dp2[u][1])dp2[u][1]=dp[v][0];
        if(dp2[u][1]>dp[u][1])swap(dp[u][1],dp2[u][1]);
    }
    isleaf[u]=end;
    if(end){
        dp[u][0]=dp[u][1]=0;
    }
    if(dp[u][0]!=INF)dp[u][0]+=a[u];
    if(dp[u][1]!=-INF)dp[u][1]+=a[u];
    if(dp2[u][0]!=INF)dp2[u][0]+=a[u];
    if(dp2[u][1]!=-INF)dp2[u][1]+=a[u];
}

void dfs2(int u,int fa){
    for(int i=head[u];~i;i=edge[i].nxt){
        int v=edge[i].v;
        if(u!=1&&v==fa)continue;
        ll minn=dp[v][1]+a[u]==dp[u][0]?dp2[u][0]:dp[u][0];//父亲极值来源于v的话,v就要继承父亲的次值
        ll maxn=dp[v][0]+a[u]==dp[u][1]?dp2[u][1]:dp[u][1];
		//叶子节点的父亲就只有一个极值,没有次值,就会出现下面情况
        if(maxn==-INF)maxn=a[u];
        if(minn==INF)minn=a[u];
        maxn+=a[v];
        minn+=a[v];
        if(isleaf[v]){
            ans=max(ans,maxn);
        }
        if(maxn<dp2[v][0])dp2[v][0]=maxn;
        if(dp2[v][0]<dp[v][0])swap(dp[v][0],dp2[v][0]);

        if(minn>dp2[v][1])dp2[v][1]=minn;
        if(dp2[v][1]>dp[v][1])swap(dp[v][1],dp2[v][1]);

        ans=max(ans,dp[v][0]);
        dfs2(v,u);
    }
}

int main(){
    int t;
    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            dp[i][0] = dp2[i][0] = INF;
            dp[i][1] = dp2[i][1] = -INF;
            head[i]=-1;
            scanf("%lld",&a[i]);
        }
        ll x;
        for(int i=1;i<=n;i++){
            scanf("%lld",&x);
            a[i]-=x;
        }
        cnt=0;
        for(int i=1,u,v;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        dfs(1,0);
        ans=dp[1][0];
        dfs2(1,0);
        printf("%lld\n",ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值