「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;
}
完结撒花❀
★,°:.☆( ̄▽ ̄)/$:.°★ 。

浙公网安备 33010602011771号