换根dp

感觉这类问题很少?算了,还是拿出来水一下吧qwq...


首先来看一道例题:POJ3585

一句话题意:树上任意源点多汇点最大流

你看这不就是个最大流的板子题吗?我先建个图,然后跑最大流,然后,,,然后?

然后

All the elements are nonnegative integers no more than 200000.

还多测... mdzz

好了来讲换根吧。

首先有一个暴力(dp),我们先考虑以(rt=1)的情况((1)为根),令(dp[x])表示(rt)为根时,直接往节点(x)灌水,然后向(x)的子树内流的最大流量

有如下转移

[dp[x] = sumlimits_{v in son_x} min{dp[v], w(x,v)} ]

特别的,如果(v)(1)为根的情况下是叶节点的话,(dp[x] += w(x,v))(w(x,y))表示边((x,y))的容量)

然后(O(n))枚举(rt),再(O(n))dp,就可以收获一个(n^2)的优秀做法。

发现换根过后树的变化实际上没有那么大,我们令(f[x])表示(rt=x)时的(dp[x])

考虑把(rt)换到(x)的一个儿子(v)上,由于(x)为根的时候(v)也是(x)的一个儿子,所以(v)会对(f[x])产生(min(dp[v],w(x,v)))的贡献

那么(f[x]-min(dp[v],w(x,v)))就得到了除了子树(v)以外的部分,它在换根后会成为(v)的一棵子树

所以(f[v] = dp[v]+minleft(w_{x,v},f[x]-min(dp[v],w_{x,v}) ight))(嘛...括号太多看不清楚...所以这里就直接(w_{x,v})了qwq)

需要特判(x)只有(v)一个儿子的时候(f[v] = dp[v]+w_{x,v})(就直接从(v)流向(x)了)

所以Code

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <set>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define fore(i,x) for(int i=head[x],v=e[i].to,w=e[i].w;i;i=e[i].nxt,v=e[i].to,w=e[i].w)
const int N=3e5+10;
struct edge
{
    int to,nxt,w;
}e[N<<1];
int head[N],cnt;
inline void ade(int x,int y,int w)
{e[++cnt]=(edge){y,head[x],w},head[x]=cnt;}
inline void addedge(int x,int y,int w)
{ade(x,y,w),ade(y,x,w);}
int n,sum[N],f[N],deg[N]; // 这里的sum就是上面的dp辣
void dfs(int x,int prev)
{
    fore(i,x) if(v!=prev)
    {
        dfs(v,x);
        if(deg[v]==1) sum[x]+=w;
        else sum[x]+=min(sum[v],w);
    }
}
void getans(int x,int prev)
{
    fore(i,x) if(v!=prev)
    {
        if(deg[x]==1) f[v]=sum[v]+w;
        else f[v]=sum[v]+min(f[x]-min(sum[v],w),w);
        getans(v,x);
    }
}
void sol()
{
    memset(deg,0,sizeof(deg));
    memset(sum,0,sizeof(sum));
    memset(f,0,sizeof(f));
    memset(head,0,sizeof(head));
    memset(e,0,sizeof(e));
    cnt=0;
    scanf("%d",&n);
    for(int x,y,w,i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&w);
        addedge(x,y,w);
        ++deg[x],++deg[y];
    }
    dfs(1,0);
    f[1]=sum[1],getans(1,0);
    int ans=0;
    for(int i=1;i<=n;i++)ans=max(ans,f[i]);
    printf("%d
",ans);
}
int main()
{
    int T; scanf("%d",&T); while(T--)sol();
    return 0;
}

于是再来看一道题:LuoguP3478

同样换根,令(f[x])表示(x)为根时的所有节点的深度和。

考虑将根换到(x)的一个儿子(v)上,那么对于子树(v)里面的节点,他们的深度会减少(1),其他节点深度会加上(1),即((size_v)表示(v)(1)为根时的子树大小)

[f[v]=f[x]+n-size_v-size_v=f[x]+n-2 imes size_v ]

先以(1)为根求出(size),然后换根就好了

十年OI一场空,不开long long见祖宗

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define fore(i,x) for(int i=head[x],v=e[i].to;i;i=e[i].nxt,v=e[i].to)
const int N=1e6+10;
int n;
struct edge
{
    int to,nxt;
}e[N<<1];
int head[N],cnt=0;
inline void ade(int x,int y)
{e[++cnt]=(edge){y,head[x]},head[x]=cnt;}
inline void addedge(int x,int y)
{ade(x,y),ade(y,x);}
ll f[N],dep[N];
int siz[N];
void dfs(int x,int prev,int deep)
{
    dep[x]=deep,siz[x]=1;
    fore(i,x) if(v!=prev)
        dfs(v,x,deep+1),siz[x]+=siz[v];
}
void getans(int x,int prev)
{
    fore(i,x) if(v!=prev)
    {
        f[v]=f[x]+n-2ll*siz[v];
        getans(v,x);
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1,x,y;i<n;i++)
        scanf("%d%d",&x,&y),addedge(x,y);
    dfs(1,0,0); 
    for(int i=1;i<=n;i++) f[1]+=dep[i];
    getans(1,0);
    ll mx=0; int ans=0;
    for(int i=1;i<=n;i++)
        if(f[i]>mx) mx=f[i],ans=i;
    printf("%d
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/wxq1229/p/12317448.html