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; 
}
posted @ 2023-09-08 10:53  FxorG  阅读(11)  评论(0编辑  收藏  举报