1. 程式人生 > 實用技巧 >題解 洛谷 P4189 【[CTSC2010]星際旅行】

題解 洛谷 P4189 【[CTSC2010]星際旅行】

一個比較直接的想法就是對每個點進行拆點,拆成入點和出點,限制放在入點和出點相連的邊上,然後跑最大費用最大流即可。

但是這樣複雜度無法接受,所以考慮模擬費用流來解決本題。

發現 \(H\) 都大於等於該節點的度數,所以從根節點出發,一定可以到達所有節點。

先考慮以根節點為起點和終點的答案,首先可以遍歷整棵樹後回到根節點,每條邊的兩個端點的 \(H\) 都減一,答案加二。然後繼續考慮每條邊的貢獻,若其兩個端點在遍歷後 \(H\) 不為 \(0\),則可以反覆走這條邊,直到其中一個端點的 \(H\)\(0\)

得出根節點的答案後,考慮如何推出其他點的答案。設當前節點為 \(x\),當前考慮的兒子為 \(y\)

。若 \(H_x\) 不為 \(0\),則可以直接從 \(x\) 走到 \(y\),使答案加一。若 \(H_x\)\(0\),那麼從 \(y\)\(x\) 後從 \(x\) 就無法到達 \(y\),所以進行反悔退流,將原先 \(y\)\(x\) 的一個流退掉,如果此時 \(y\) 存在一個兒子的 \(H\) 不為 \(0\),那麼其就可以反覆走和這個兒子相連的邊,使答案加一,若沒有,則答案減一。

同時還要注意在 \(dfs\) 時需要回溯。

#include<bits/stdc++.h>
#define maxn 100010
using namespace std;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int n,tot;
int v[maxn],ans[maxn],son[maxn];
struct edge
{
    int to,nxt;
}e[maxn];
int head[maxn],edge_cnt;
void add(int from,int to)
{
    e[++edge_cnt]=(edge){to,head[from]};
    head[from]=edge_cnt;
}
void dfs_pre(int x,int fa)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to,val;
        if(y==fa) continue;
        dfs_pre(y,x),val=min(v[x],v[y]);
        v[x]-=val,v[y]-=val,tot+=val*2;
        if(v[y]) son[x]=y;
    }
}
void dfs_ans(int x,int fa)
{
    ans[x]=tot;
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to,type;
        if(y==fa) continue;
        if(v[x]) v[x]--,tot++,type=1;
        else if(son[y]) v[son[y]]--,tot++,type=2;
        else v[y]++,tot--,type=3;
        dfs_ans(y,x);
        if(type==1) v[x]++,tot--;
        if(type==2) v[son[y]]++,tot--;
        if(type==3) v[y]--,tot++;
    }
}
int main()
{
    read(n);
    for(int i=1;i<=n;++i) read(v[i]);
    for(int i=1;i<n;++i)
    {
        int x,y;
        read(x),read(y),x++,y++;
        add(x,y),add(y,x),v[x]--,v[y]--,tot+=2;
    }
    dfs_pre(1,0),dfs_ans(1,0);
    for(int i=1;i<=n;++i) printf("%d\n",ans[i]);
    return 0;
}