P3647 [APIO2014]連珠線

題意

傳送門

題解

我們發現,如果一棵樹的形態固定了,那麼藍線的方向一定是son[x]-x-fa[x],那麼我們就可以先隨便定一個根進行DP。

我們設f[i][0]f[i][0]表示以ii爲根的子樹中,且ii不作爲藍線的中點能夠得到的最大價值。同理,設f[i][1]f[i][1]表示以ii爲根的子樹中,ii作爲藍線的中點能夠得到的最大價值。

我們分別對於兩種情況分析。f[i][0]f[i][0]較爲簡單,對於一個json[i]j\in son[i],設wjw_ji,ji,j之間的邊權,要使ii不作爲藍線的中點,則滿足jj爲藍線中點(即f[j][1]+wjf[j][1]+w_j),或之間是紅線(即f[j][0]f[j][0])。所以f[i][0]=json[i]max(f[j][0],f[j][1]+wj)f[i][0] = \sum\limits_{j\in son[i]}\max(f[j][0], f[j][1]+w_j)

然後考慮ii爲中點的情況,顯然ii只能是一條藍線的中點,所以我們可以枚舉這條藍線連接的兒子(設爲jj),那麼其餘兒子依舊是按照f[i][0]f[i][0]的方式轉移,所以我們將f[i][1]f[i][1]初始化爲f[i][0]f[i][0]。對於jj,我們減去之前的貢獻,再加入藍線的貢獻。由於ii是中點,所以jj的貢獻即爲f[j][0]+wjf[j][0]+w_j。綜上,f[i][1]=f[i][0]+maxjson[i](f[j][0]+wjmax(f[j][0],f[j][1]+wj))f[i][1]=f[i][0]+\max\limits_{j\in son[i]}(f[j][0]+w_j-\max(f[j][0],f[j][1]+w_j))

說了這麼多,其實都只是在樹的結構固定的前提下進行的。當整棵樹的結構不確定時,我們就需要通過換根操作統計答案。我們發現換根對於大部分節點並沒有影響,於是我們就可以通過一些奇技淫巧進行O(1)O(1)換根。

我們考慮一個點的兒子變成了父親會發生什麼影響。首先這個兒子的貢獻消失了,隨之而來的可能是轉移方程中的最大值也消失了,所以我們就需要記錄次大值(經典套路)。同時當前點會變成兒子對原來的兒子(現在的父親)產生貢獻。

所以我們要在第一次DP中記錄一個dp[i][0/1][j]dp[i][0/1][j]表示在f[i][0/1]f[i][0/1]這個狀態的統計過程中,不考慮第jj個兒子得到的答案。對於dp[i][0][j]dp[i][0][j]直接從總和中減去;對於dp[i][1][j]dp[i][1][j],維護次大值更新即可。

下面正式開始換根,我們在dfsdfs過程中,枚舉當前節點xx的兒子作爲整棵樹的根,此時值得注意的是,由於換根後,xx的父親會變成他的兒子,所以我們並不能直接在xx和兒子之間換根,而是應該先重新計算fa[x]fa[x]xx的貢獻,然後再進行換根。具體操作可以看代碼,十分容易理解。

代碼

#include <bits/stdc++.h>
#define MAX 500005
#define INF 0x3f3f3f3f
#define c(x) (f[x][0]+cost[i]-max(f[x][0], f[x][1]+len[x]))		//狀態轉移方程
using namespace std;

int n, cnt;
int head[MAX], vet[MAX], Next[MAX], cost[MAX];

void add(int x, int y, int w){
    cnt++;
    Next[cnt] = head[x];
    head[x] = cnt;
    vet[cnt] = y;
    cost[cnt] = w;
}

int par[MAX], len[MAX];
int f[MAX][2];       //f[i][0]表示i不做中點,f[i][1]表示做中點
vector<int> son[MAX], dp[MAX][2], mx[MAX];
void dfs(int x, int fa){
    f[x][0] = 0, f[x][1] = -INF;
    int mx1 = -INF, mx2 = -INF;
    for(int i = head[x]; i; i = Next[i]){
        int v = vet[i];
        if(v == fa) continue;
        len[v] = cost[i], par[v] = x;
        son[x].push_back(v);
        dfs(v, x);
        f[x][0] += max(f[v][0], f[v][1]+cost[i]);
        if(c(v) > mx1) mx2 = mx1, mx1 = c(v);		//記錄最大值和次大值
        else if(c(v) > mx2) mx2 = c(v);
    }
    f[x][1] = f[x][0]+mx1;
    for(int i = head[x]; i; i = Next[i]){
        int v = vet[i];
        if(v == fa) continue;
        dp[x][0].push_back(f[x][0]-max(f[v][0], f[v][1]+cost[i]));
        if(c(v) == mx1){		//通過最大值和次大值計算dp[x][1]
            dp[x][1].push_back(dp[x][0].back()+mx2);
            mx[x].push_back(mx2);
        }
        else{
            dp[x][1].push_back(dp[x][0].back()+mx1);
            mx[x].push_back(mx1);
        }
    }
}

int ans = 0;
void solve(int x){     //換根
    for(int i = 0; i < son[x].size(); i++){
        f[x][0] = dp[x][0][i], f[x][1] = dp[x][1][i];
        if(par[x]){     //重新統計父親對x的貢獻。
            f[x][0] += max(f[par[x]][0], f[par[x]][1]+len[x]);
            f[x][1] = f[x][0] + max(mx[x][i], f[par[x]][0]+len[x]-max(f[par[x]][0], f[par[x]][1]+len[x]));
        }
        ans = max(ans, f[son[x][i]][0]+max(f[x][0], f[x][1]+len[son[x][i]]));
        solve(son[x][i]);
    }
}

int main()
{
    cin >> n;
    int x, y, w;
    for(int i = 1; i < n; i++){
        scanf("%d%d%d", &x, &y, &w);
        add(x, y, w);
        add(y, x, w);
    }
    dfs(1, 0);
    solve(1);
    cout << ans << endl;

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章