P6122 [NEERC2016] Mole Tunnels
https://www.luogu.com.cn/problem/P6122
题目明显有一个匹配的问题,费用流的建模也很明显。
- 考虑当前在某个时刻,睡醒的所有鼹鼠,都有一条 \(S\to i\) 的边,容量为 \(1\),费用为 \(0\)。
- 对于树上一条边 \(u->v\),注意,这里是把无向边看成两条单向边。连 \(u\to v\),容量为无穷大,费用为 1。
- 对于一个食物,连 \(i\to T\),容量为 \(c_i\),费用为 \(0\)。
这是一个匹配模型。对于任意时刻,要求所有 \(S->i\) 的边流满。
考虑模拟 EK 去找费用流。
加入一只鼹鼠 \(x\),那么 \(S\to x\) 要流满,前面的鼹鼠 \(y\) 的 \(S\to y\) 也流满,且一定不会退流,因为如果退流的话,总流量减少,显然是不优的,因为我们每次是找费用最低的增广路,这个总流量是会单增的。
既然总流量单增,并且一定流满,那么对于一个负环来说,是不是原先的就并不会流满啊?并且负环能提供的流量增量为 0,因为要退掉之前的流量。那么,我增量的时候,找增广路,显然只会有一条,因为 \(S\to i\) 容量为 1。要求增广完网络满流,显然是不允许出现负环。
那么,我们只需要考虑 \(S\to x\to p \to T\) 这样子的源汇路径带来的增广。
注意到,我们一定是找一条费用最低的可流路径去增广,这是不是相当于,对于 \(x\) 找到距离它最近的粮食的点,并流过去。并且流过去后需要注意对路径上的所有反向边的流量增加 1。
那么问题很简单了。
-
对于 \(x\) 找到距离它最近的粮食的点。
-
增加一条权为 \(-1\) 的边。
-
减少一条权为 \(-1\) 的边。
-
减少某个点的粮食。
注意到,对于第一点,我们可以大力枚举两点的 \(LCA\) 做,因为树高 \(\log n\),那么就变成维护一个点,向下到粮食的距离了。
但是,减少对于某些并不满足求逆的东西来说,并不是很好做。我们考虑信息是合并上去的,这样子,我们规避了求逆这个东西。
我们发现,树的形态很特别,只有左右儿子,因此我们可以类线段树,完成对信息的合并。
接下来的东西都很常规了,就做完了。
#include <bits/stdc++.h>
#define il inline
#define ll long long
#define pb push_back
#define not_son(x) ((x)*2>n)
#define is_ls(x) ((x)*2<=n)
#define is_rs(x) ((x)*2+1<=n)
using namespace std;
const int N=(int)(1e5+5),inf=(int)(2e9),Lim=(int)(1e5);
int n,m,c[N],up2[N],down2[N],dis[N],disp[N],ans;
il void push_up(int x) {
if(c[x]) dis[x]=0,disp[x]=x;
else dis[x]=inf,disp[x]=0;
if(is_ls(x)) {
int ls=x*2;
if(dis[ls]<inf) {
if(down2[ls]) {
if(dis[ls]-1<dis[x]) {
dis[x]=dis[ls]-1;
disp[x]=disp[ls];
}
}
if(dis[ls]+1<dis[x]) {
dis[x]=dis[ls]+1;
disp[x]=disp[ls];
}
}
}
if(is_rs(x)) {
int rs=x*2+1;
if(dis[rs]<inf) {
if(down2[rs]) {
if(dis[rs]-1<dis[x]) {
dis[x]=dis[rs]-1;
disp[x]=disp[rs];
}
}
if(dis[rs]+1<dis[x]) {
dis[x]=dis[rs]+1;
disp[x]=disp[rs];
}
}
}
}
il void build(int x) {
if(not_son(x)) {
push_up(x);
return ;
}
if(is_ls(x)) build(x*2);
if(is_rs(x)) build(x*2+1);
push_up(x);
}
#define another(x) (((x)&1)?((x)-1):((x)+1<=n)?((x)+1):0)
#define cal_up(x) (up2[x]?(-1):1)
#define cal_down(x) (down2[x]?(-1):1)
il void fanxiang_up(int x,int y) {
// cout<<x<<" "<<y<<'\n';
while(x!=y) {
if(cal_up(x)==-1) {
--up2[x];
} else {
++down2[x];
}
push_up(x);
x>>=1;
}
push_up(x);
}
il void fanxiang_down(int x,int y) {
// cout<<x<<" down "<<y<<'\n';
while(x!=y) {
if(cal_down(x)==-1) {
--down2[x];
} else {
++up2[x];
}
push_up(x);
x>>=1;
}
push_up(x);
}
il void del(int p) {
--c[p];
push_up(p);
while(p) {
p>>=1;
if(!p) break ;
push_up(p);
}
}
signed main() {
cin.tie(0); ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>c[i];
build(1);
for(int i=1;i<=m;i++) {
int x; cin>>x; int P=x;
int qwq=0,pos=0,res=inf,LCA=0;
if(dis[x]<Lim) {
res=dis[x];
pos=disp[x];
LCA=x;
}
while(1) {
// cout<<x<<'\n';
int prex=x;
x>>=1; if(!x) break ;
qwq+=cal_up(prex);
if(another(prex)) {
int p=another(prex);
if(dis[p]<Lim) {
int v=qwq+cal_down(p)+dis[p];
if(v<res) {
res=v; LCA=x; pos=disp[p];
}
}
}
if(c[x]) {
if(qwq<res) {
res=qwq; LCA=x; pos=x;
}
}
}
ans+=res;
cout<<ans<<' ';
// cout<<"ans "<<ans<<'\n';
fanxiang_up(P,LCA);
fanxiang_down(pos,LCA);
del(pos);
}
return 0;
}