「APIO2014」连珠线 解题报告

题目描述

在达芬奇时代,有一个流行的儿童游戏称为连珠线。当然,这个游戏是关于珠子和线的。线是红色或蓝色的,珠子被编号为 \(1\)\(n\)。这个游戏从一个珠子开始,每次会用如下方式添加一个新的珠子: Append(w, v):一个新的珠子 \(w\) 和一个已经添加的珠子 \(v\) 用红线连接起来。 Insert(w, u, v):一个新的珠子 \(w\) 插入到用红线连起来的两个珠子 \(u, v\) 之间。具体过程是删去 \(u, v\) 之间红线,分别用蓝线连接 \(u, w\)\(w, v\)。 每条线都有一个长度。游戏结束后,你的最终得分为蓝线长度之和。 给你连珠线游戏结束后的游戏局面,只告诉了你珠子和链的连接方式以及每条线的长度,没有告诉你每条线分别是什么颜色。 你需要写一个程序来找出最大可能得分。即,在所有以给出的最终局面结束的连珠线游戏中找出那个得分最大的,然后输出最大可能得分。
输入输出格式
输入格式

第一行一个正整数 \(n\),表示珠子的数量。珠子从 \(1\)\(n\) 编号。 接下来 \(n - 1\) 行每行三个整数 \(a_i, b_i, c_i\)。保证 \(1 \leq a_i < b_i \leq n\)\(1 \leq c_i \leq 10000\)。表示 \(a_i\) 号珠子和 \(b_i\) 号珠子间连了长度为 \(c_i\) 的线。
输出格式

输出一个整数,表示最大可能得分。

SOLUTION

这个题。。。。

换根dp经典套路:这个儿子的贡献消失了,随之而来的可能是转移方程中的最大值也消失了,所以我们就需要记录次大值

可以发现,如果确定了根,蓝线连的点就形如:fa—x—son

凭感觉可以dp

\(dp_{i,1/0}\)\(i\)是/否为蓝线中点的最大答案

\(f_{i,0}=\sum{max(f_{y,0},f_{y,1}+w_y)}\)

\(f_{i,1}=f_{i,0}+max(f_{y,0}+w_y-max(f_{y,0},f_{y,1}+w_y))\)

转移方程解释:

由于i只可能为一条蓝线的中点

所以我们枚举i蓝线连的点

又由于其他点是和\(f_{i,0}\)一样转移的

所以我们减去\(y\)\(f_{i,0}\)的贡献,加上作为\(x\)蓝线上儿子的贡献

当整棵树的结构不确定时,我们就需要通过换根操作统计答案。

我们考虑一个点的儿子变成了父亲会发生什么影响。

首先这个儿子的贡献消失了,随之而来的可能是转移方程中的最大值也消失了,所以我们就需要记录次大值(经典套路)

同时当前点会变成儿子对原来的儿子(现在的父亲)产生贡献

所以我们要在第一次DP中记录一个\(dp_{i,0/1,j}\)表示在\(f_{i,0/1}\)这个状态的统计过程中

不考虑第j个儿子得到的答案。对于\(dp_{i,0,j}\)直接从总和中减去

对于\(dp_{i,1,j}\)维护次大值更新即可

换根时,在dfs过程中,枚举当前节点x的儿子作为整棵树的根

此时值得注意的是:

由于换根后,x的父亲会变成他的儿子

所以我们并不能直接在x和儿子之间换根

应该先重新计算fa对x的贡献

然后再进行换根

CODE

#include<bits/stdc++.h>
using namespace std;
//dp[x][1/0]:i是/否作为蓝线中点 
const int N=1e6+2,INF=1e9;
int dp[N][2],len[N],to[N],cnt,nxt[N],hd[N],w[N],b[N];
int ans,x,y,z,n;
vector<int>son[N],f[N][2],mx[N];
void add(int x,int y,int z){
    to[++cnt]=y;
    nxt[cnt]=hd[x];
    hd[x]=cnt;
    w[cnt]=z;
} 
int c(int x,int i){return dp[x][0]+w[i]-max(dp[x][0],dp[x][1]+len[x]);}
void dfs(int x,int fa){
    dp[x][0]=0,dp[x][1]=-INF;
    int mx1=-INF,mx2=-INF;//最大值和次大值 
    for(int i=hd[x];i;i=nxt[i]){
        int y=to[i];
        if(y==fa) continue;
        len[y]=w[i];//(b[y],y)的长度 
        son[b[y]=x].push_back(y);
        dfs(y,x);
        dp[x][0]+=max(dp[y][0],dp[y][1]+w[i]);
        int val=c(y,i);
        if(val>mx1) mx2=mx1,mx1=val;
        else if(val>mx2) mx2=val;
    }
    dp[x][1]=dp[x][0]+mx1;
    for(int i=hd[x];i;i=nxt[i]){ 
        int y=to[i];
        if(y==fa) continue;
        f[x][0].push_back(dp[x][0]-max(dp[y][0],dp[y][1]+w[i]));
        int val=c(y,i);
        int in=f[x][0].back();
        if(val==mx1){
            in+=mx2;
            mx[x].push_back(mx2);
        }else{
            in+=mx1;
            mx[x].push_back(mx1);
        }
        f[x][1].push_back(in);
    }
}
//f[x][1/0][i]:dp[x][1/0]不考虑i得到的答案 
void dfs(int x){//换根 
    int fa=b[x];
    for(int i=0;i<(int)son[x].size();i++){//重新计算father的价值 
        dp[x][0]=f[x][0][i],dp[x][1]=f[x][1][i]; //不考虑i子树贡献的dp 
        int y=son[x][i];
        if(b[x]){
            dp[x][0]+=max(dp[fa][0],dp[fa][1]+len[x]);//加上fathter的贡献 
            dp[x][1]=dp[x][0]+max(mx[x][i],dp[fa][0]+len[x]-max(dp[fa][0],dp[fa][1]+len[x]));
            //i只能是一条蓝线的中点,所以在fa和mx_id中选择贡献较大的 
        }
        ans=max(ans,dp[y][0]+max(dp[x][0],dp[x][1]+len[y]));//以y为根的答案 
        dfs(y);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z),add(y,x,z);
    } 
    dfs(1,0);
    dfs(1);
    printf("%d",ans);
    return 0;
}

完结撒花❀
★,°:.☆( ̄▽ ̄)/$:.°★

posted @ 2022-10-09 19:05  _Youngxy  阅读(84)  评论(0)    收藏  举报