USACO December 铂金Maxflow
USACO 2015 December Contest, Platinum
Problem 1. Max Flow
Farmer John has installed a new system of N−1 pipes to transport milk between the N stalls in his barn (2≤N≤50,000 ), conveniently numbered 1…N . Each pipe connects a pair of stalls, and all stalls are connected to each-other via paths of pipes.
FJ is pumping milk between K pairs of stalls (1≤K≤100,000 ). For the i th such pair, you are told two stalls si and ti , endpoints of a path along which milk is being pumped at a unit rate. FJ is concerned that some stalls might end up overwhelmed with all the milk being pumped through them, since a stall can serve as a waypoint along many of the K paths along which milk is being pumped. Please help him determine the maximum amount of milk being pumped through any stall. If milk is being pumped along a path from si to ti , then it counts as being pumped through the endpoint stalls si and ti , as well as through every stall along the path between them.
INPUT FORMAT (file maxflow.in):
The first line of the input contains N and K .The next N−1 lines each contain two integers x and y (x≠y ) describing a pipe between stalls x and y .
The next K lines each contain two integers s and t describing the endpoint stalls of a path through which milk is being pumped.
OUTPUT FORMAT (file maxflow.out):
An integer specifying the maximum amount of milk pumped through any stall in the barn.
SAMPLE INPUT:
5 10 3 4 1 5 4 2 5 4 5 4 5 4 3 5 4 3 4 3 1 3 3 5 5 4 1 5 3 4
SAMPLE OUTPUT:
9
Problem credits: Brian Dean
译:
农夫约翰已经安装了N-1个管道系统给N(2≤N≤50,000)个摊位输送牛奶,方便编号为1... N。每个管道连接一对摊位,所有的摊位都通过管道路径连接起来。
FJ在K对点(1≤K≤100,000)之间抽取牛奶。对于第i个这样的对,你被告知两个点Si和Ti,表示抽取牛奶路径的两个端点,这一路径上都是同一个单位的费率。 FJ关注的是,一些点最终可能会被所有抽取的牛奶通过。请帮助他确定所有点可能通过的最多的牛奶量。如果牛奶正在沿着路径Si到Ti,则认为它将通过端点Si和Ti,以及通过沿它们之间的路径中的每个点。
INPUT FORMAT (file maxflow.in):
The first line of the input contains N and K.
第一行两个整数N和K
The next N−1 lines each contain two integers x and y (x≠y) describing a pipe between stalls x and y.
接下来N-1行,每行两个整数X和Y,描述被一个管子连接的两个点X和Y
The next K lines each contain two integers s and t describing the endpoint stalls of a path through which milk is being pumped.
接下来K行,每行两个整数 S和T,描述正在被抽取牛奶所在的两个端点
OUTPUT FORMAT (file maxflow.out):
An integer specifying the maximum amount of milk pumped through any stall in the barn.
输出一个整数,表示通过任意点所能抽取的最大牛奶数量。
树上查分
拿到这题第一反应显然是树剖。。但实际上有更加方便的做法,那就是树上差分。
首先我们联想一下对数列进行差分的做法。序列差分这个技巧一般适用于:执行若干次区间加减,到最后再统计每个点的权值。设T是A的差分序列,我们把将a~b这个区间中的每个点加上c的操作(A[i]+=c a<=i<=b)转变成对T的操作T[a]+=c,T[b+1]-=c,最后A[k]的值就是Sum{T[i]}(1<=i<=k)即T的一个前缀和。这个很容易能理解,随便在脑中yy一下或者画画图都能理解。
然后我们再来看下这道题。这道题与序列上有一定相似之处:执行若干次路径上的加减,最后统计每个点的权值。我们可以将在序列上进行差分的方法拓展到树上。对于s~t这个路径上每个点加上c的操作,我们可以对树A的“差分树”T进行操作:T[s]+=c,T[t]+=c,T[LCA(s,t)]-=c,T[Father[LCA(s,t)]]-=c。即给s这个点加上c,给t这个店加上c,给s和t的最近公共祖先和这个祖先的父亲减去c。最后每个点的权值就是这个点的子树权值之和。
这样做为什么是正确的呢?我们来yy一下一般情况:对于一棵树,从s走到t可以这么走:先从s走到LCA(s,t),再从t走到LCA(s,t)。为了更方便接下来的叙述,我们可以想象有两个人分别从s和t走到LCA(s,t)。然后,我们要将路径s~t上每个点加上c,因为我们最后统计的是每个点的子树权值之和,所以说白了就是这个标记要向它的父亲上传;所以当我们给s和t都加上c后,上传到LCA(s,t)的时候就变成了2c,显然在这里是要减去c的;而对于LCA(s,t)的父亲,由于LCA(s,t)的标记也要上传,所以在LCA(s,t)的父亲处再减去一个c。这段话比较长,但是只要深刻理解数列差分的技巧,理解树上差分也是很容易的。
最后说几个实现上的细节。
第一,对于LCA(s,t),如果它是根,那么就没有将它父亲减去c的操作了。
第二,最后子树统计的时候的方法比较多。可以用类似拓扑排序的思想,从最后一层结点“逐层上传”。也可以用树型dp。这个随个人喜好而定。
具体看代码~
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<cmath> #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #define maxn 50010 #define maxk 100010 using namespace std; int n,k,s,t,tot,lca,root=1,ans=0,f[maxn],deep[maxn],value[maxn],head[maxn],p[maxn][20]; struct node{ int go,next; }e[maxk]; inline int read(){ int x=0,f=1;char ch=getchar(); while (ch>'9' || ch<'0'){if (ch=='-') f=-1;ch=getchar();} while (ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void addedge(int &s,int &t){ e[++tot]=(node){t,head[s]};head[s]=tot; e[++tot]=(node){s,head[t]};head[t]=tot; } void make_tree(int u,int fa){ int v; for (int i=head[u];i;i=e[i].next){ v=e[i].go; if (v!=fa){ deep[v]=deep[u]+1; f[v]=u; make_tree(v,u); } } } void prepare(){ memset(p,-1,sizeof(p)); for (int i=1;i<=n;i++) p[i][0]=f[i]; for(int j=1;(1<<j)<=n;j++) for (int i=1;i<=n;i++) if (p[i][j-1]!=-1) p[i][j]=p[p[i][j-1]][j-1]; } inline int LCA(int a,int b){ if (deep[a]<deep[b]) swap(a,b); int t=trunc(log2(deep[a])); for (int i=t;i>=0;i--) if (deep[a]-(1<<i)>=deep[b]) a=p[a][i]; if (a==b) return a; for (int i=t;i>=0;i--) if (p[a][i]!=-1 && p[a][i]!=p[b][i]) a=p[a][i],b=p[b][i]; return p[a][0]; } int ask(int x){ for (int i=head[x];i;i=e[i].next){ if (f[x]!=e[i].go){ ask(e[i].go); value[x]+=value[e[i].go]; } } ans=max(ans,value[x]); } int main(){ // freopen("maxflow.in","r",stdin); // freopen("maxflow.out","w",stdout); n=read();k=read(); for (int i=1;i<n;i++){ s=read();t=read(); addedge(s,t); } deep[root]=1; make_tree(root,f[root]=0); prepare(); for (int i=1;i<=k;i++){ s=read();t=read(); lca=LCA(s,t); value[s]++;value[t]++;value[lca]--;value[f[lca]]--; } ask(root); printf("%d\n",ans); }