题解 洛谷 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
#define maxn 100010
using namespace std;
template 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

你可能感兴趣的:(题解 洛谷 P4189 【[CTSC2010]星际旅行】)