【洛谷6122】[NEERC2016] Mole Tunnels(模拟费用流)
- 一棵\(n\)个点的完全二叉树,第\(i\)个点可以容纳\(a_i\)只鼹鼠。
- 每次操作在某个节点增加一只鼹鼠。假设当前所有鼹鼠分别走到某个节点使得所有节点上鼹鼠的实际个数都不超过可容纳个数,求所需的最小总路径长度。
- \(n,q\le10^5\)
模拟费用流
先考虑费用流怎么做:
- 从超级源向每个点连容量为鼹鼠只数、代价为\(0\)的边。
- 从每个点向超级汇连容量为可容纳数、代价为\(0\)的边。
- 树上相邻两点间互连容量为\(INF\)、代价为\(1\)的边。
一次操作相当于增加超级源到\(x\)的一点容量。
这一过程需要找到一条从\(x\)出发的一条最短路,实际上并不用每次跑\(SPFA\)找,只用维护一下\(f_x,id_x\)表示\(x\)走到子树内一个到超级汇容量不为\(0\)的点的最短路及这个点的编号即可。
由于这是一棵完全二叉树,可以直接暴力枚举折点。
假设找到的最短路是\(x\rightarrow u\rightarrow v\)(\(u\)是折点),首先将\(v\)到超级汇的容量减\(1\)。
然后,将\(x\rightarrow u\)(不包括\(u\))所有点标记增加一条从父节点到当前点的费用为\(-1\)的边用于退流(或者减少一条从当前点到父节点的费用为\(-1\)的边,二者可以合成一个\(p_x\)存储,\(p_x>0\)表示存在\(p_x\)条从父节点到当前点的费用为\(-1\)的边,\(p_x<0\)表示存在\(-p_x\)条从当前点到父节点的费用为\(-1\)的边),将\(v\rightarrow u\)(不包括\(u\))所有点标记增加一条从当前点到父节点的费用为\(-1\)的边(或这减少一条从父节点到当前点的费用为\(-1\)的边)。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define INF (int)1e9
#define F5(x,v,p) (f[x]>v&&(f[x]=v,id[x]=p))
#define PU(x) (f[id[x]=x]=a[x]?0:INF,F5(x,f[x<<1]+(p[x<<1]>0?-1:1),id[x<<1]),F5(x,f[x<<1|1]+(p[x<<1|1]>0?-1:1),id[x<<1|1]))//上传信息
using namespace std;
int n,a[N+5],f[2*N+5],id[2*N+5],p[2*N+5];
I void Init(CI x=1) {(x<<1)<=n?(Init(x<<1),0):f[x<<1]=INF,(x<<1|1)<=n?(Init(x<<1|1),0):f[x<<1|1]=INF,PU(x);}//预处理
int ans;I void Solve(RI x)//从x出发找一条最短路
{
RI o=x,w=0,u,v,d=1e9;W(o) f[o]+w<d&&(d=f[o]+w,u=o,v=id[o]),w+=p[o]<0?-1:1,o>>=1;ans+=d;//暴力上跳枚举折点
--a[v],PU(v);W(v^u) --p[v],v>>=1,PU(v);W(x^u) ++p[x],x>>=1,PU(x);W(x>>=1) PU(x);//更新v到超级汇的容量,更新v,x到u的信息,u到根上传信息
}
int main()
{
RI Qt,i,x;for(scanf("%d%d",&n,&Qt),i=1;i<=n;++i) scanf("%d",a+i);
Init();W(Qt--) scanf("%d",&x),Solve(x),printf("%d ",ans);return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒