洛谷 P3647 [APIO2014]连珠线(换根 dp)
阅读原文时间:2023年07月09日阅读:1

题面传送门

题意:

桌子上有 \(1\) 个珠子,你要进行 \(n-1\) 次操作,每次操作有以下两种类型:

  1. 拿出一个新珠子,并选择一个桌子上的珠子,在它们之间连一条红线
  2. 选择两个由红线相连的珠子 \(u,v\),并拿出一个新珠子 \(w\),将原来连接 \(u,v\) 的红线断开,并在 \(u,w\) 和 \(v,w\) 之间各连一条蓝线。

显然最后 \(n\) 个珠子会形成一棵由 \(n-1\) 条线的树,给出最终每条线的长度,但你不知道它们的颜色。

你的得分为所有蓝线长度之和,求在所有可能的情况下,你得分的最大值。

\(n \in [2,2\times 10^5]\)

考虑蓝线的性质。由于连成的蓝线就没办法再被断开了,故蓝线连接的三个节点 \(u,w,v\) 在最终的树上也是相邻的。

故原题可以转化为:你可以选择一条三个节点 \(u\to v\to w\) 的链满足边 \((u,v),(v,w)\) 都没被选择,要求选出的边的权值之和的最大值。

树上相邻的三个节点有两种可能,一是爷爷->父亲->儿子,二是儿子->父亲->兄弟。

第二种情况比较复杂。不过我们可以枚举最开始的珠子在最终的树上的编号是多少,也就是钦定一个根节点,这样就不会出现第二种情况(很好理解,如果出现儿子->父亲->兄弟的情况,那我们肯定是先连好儿子->兄弟的边,再插入父亲节点,而由于父亲节点与根节点相连,所以应当是父亲节点先连好才对,所以这种情况不可能出现)

然后就可以 \(dp\) 了。\(dp_{u,0}\) 选好了 \(u\) 的子树中的边,\(u\) 不是某条蓝链的中点的最大权值和。\(dp_{u,1}\) 表示 \(u\) 是某条蓝链的中点的最大权值和。

考虑转移,\(dp_{u,0}\) 比较简单,枚举它的每个儿子 \(v\),有两种情况,要么 \((u,v)\) 间连了条红线,也就是 \(dp_{v,0}\),要么 \((u,v)\) 间连了条蓝线,而由于 \(u\) 不是某条蓝链的中点,所以这条链的连法只可能是 \(u\to v\to v\) 的某个儿子,也就是 \(dp_{v,1}+w\),故 \(dp_{u,0}=\sum\limits_{v\in son_u}\max(dp_{v,0},dp_{v,1}+w)\)

再考虑 \(dp_{u,1}\),显然 \(dp_{u,1}\) 是在 \(dp_{u,1}\) 是在 \(dp_{u,0}\) 的基础上将 \(u\) 与某个儿子 \(v\) 节点之间的边换成蓝边,考虑这一类树形 \(dp\) 的常用套路,枚举这个儿子 \(v\),计算将 \((u,v)\) 之间的边改为蓝边造成的 \(\Delta=dp_{v,0}+w-\max(dp_{v,0},dp_{v,1}+w)\),然后取个 \(\max\) 即可,故 \(dp_{u,1}=dp_{u,0}+\max\limits_{v\in son_u}dp_{v,0}+w-\max(dp_{v,0},dp_{v,1}+w)\)

然后考虑换根,记 \(f_{u}\) 表示以 \(u\) 为根节点的答案,\(out_{i,0/1}\) 表示 \(i\) 子树外的 \(dp\) 值,加法是可逆的,至于 \(\max\), multiset 维护一下即可。这一部分比较套路,具体见代码。

时间复杂度 \(\mathcal O(n\log n)\)

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
typedef pair<int,int> pii;
typedef long long ll;
const int MAXN=2e5+5;
int n,to[MAXN<<1],nxt[MAXN<<1],hd[MAXN],cst[MAXN<<1],ec=0;
void adde(int u,int v,int w){
    to[++ec]=v;nxt[ec]=hd[u];cst[ec]=w;hd[u]=ec;
}
int dp[MAXN][2],f[MAXN],out[MAXN][2];
void dfs(int x,int fa){
    dp[x][1]=-2e9;
    for(int e=hd[x];e;e=nxt[e]){
        int y=to[e],z=cst[e];if(y==fa) continue;dfs(y,x);
        dp[x][0]+=max(dp[y][0],dp[y][1]+z);
        dp[x][1]=max(dp[x][1],dp[y][0]+z-max(dp[y][0],dp[y][1]+z));
    } dp[x][1]+=dp[x][0];
}
void cgrt(int x,int fa){
    int sum=0;
    multiset<int> st;st.insert(-2e9);
    for(int e=hd[x];e;e=nxt[e]){
        int y=to[e],z=cst[e];
        if(y==fa){
            sum+=max(out[x][0],out[x][1]+z);
            st.insert(out[x][0]+z-max(out[x][0],out[x][1]+z));
        } else {
            sum+=max(dp[y][0],dp[y][1]+z);
            st.insert(dp[y][0]+z-max(dp[y][0],dp[y][1]+z));
        }
    }
    for(int e=hd[x];e;e=nxt[e]){
        int y=to[e],z=cst[e];if(y==fa) continue;
        out[y][0]=sum-max(dp[y][0],dp[y][1]+z);
        st.erase(st.find(dp[y][0]+z-max(dp[y][0],dp[y][1]+z)));
        out[y][1]=out[y][0]+(*st.rbegin());
        st.insert(dp[y][0]+z-max(dp[y][0],dp[y][1]+z));
        f[y]=dp[y][0]+max(out[y][0],out[y][1]+z);
        cgrt(y,x);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        adde(u,v,w);adde(v,u,w);
    } dfs(1,0);f[1]=dp[1][0];cgrt(1,0);int ans=0;
    for(int i=1;i<=n;i++) ans=max(ans,f[i]);
    printf("%d\n",ans);
    return 0;
}