BZOJ4849 [Neerc2016]Mole Tunnels

84 篇文章 1 订阅
7 篇文章 0 订阅

考虑如果给定一个K算答案怎么算,我们可以费用流,每个树边建流量INF费用1,每个点向T连流量c[x]费用0,对于前k个鼹鼠,S向每个鼹鼠出现的位置连一个流量1费用0的边,然后跑最小费用最大流即可求出最小代价

那么如果是对于每个K都要求的话,我们可以考虑令S连向第i个鼹鼠出现位置的边费用为(m-i+1)*INF,这样的话第i次曾广一定会走第i个鼹鼠出现的位置,那么増广i次之后得到的就是前i个鼹鼠的答案

注意是不能第i次増广之前新加一条S到第i个鼹鼠出现位置的边的,这样做的话会出负环

然而我们发现这个数据范围的话跑n遍spfa会T

那么就是一些在特殊图上用其他算法算网络流的套路了

我们发现这是一棵树,而每次找増广路就是在所有c>0的点中寻找一个离起点最近的,然后把那个点的c减一,并更新路径上的后向弧相关的信息,也就是把一些边的边权由1改成-1或者由-1改成1

这玩意乍一看挺不可做的,然而我们发现题目里还对树的形态作了特殊限制,i的父亲是i/2

那么这就很棒棒了,这棵树的树高是log的,那么任意两点间路径长度也是log级别的,并且每个点最多只有两个儿子

我们对每个点维护他子树里到他最近的c>0的点的距离以及是哪个点,然后枚举lca即可算出离开始点最近的c>0的点以及距离

然后暴力更新dp值和边权即可

复杂度O(n log n)

#include<iostream>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<cstdlib>
#include<cstdio>
#include<map>
#include<bitset>
#include<set>
#include<stack>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 200010
#define MAXM 1010
#define ll long long
#define eps 1e-8
#define MOD 1000000007
#define INF 1000000000
int n,m;
int rem[MAXN];
int pos[MAXN];
int f[MAXN],ft[MAXN];
int vu[MAXN],vd[MAXN],cu[MAXN],cd[MAXN];
ll ans;
void ud(int x){
	if(f[x<<1]<=f[x<<1|1]){
		f[x]=f[x<<1];
		ft[x]=ft[x<<1];
	}else{
		f[x]=f[x<<1|1];
		ft[x]=ft[x<<1|1];
	}
	if(rem[x]&&f[x]>0){
		f[x]=0;
		ft[x]=x;
	}
	f[x]+=vd[x];
}
void find(int x){
	int i;
	int now=0;
	int mn=f[x]-vd[x],mnt=ft[x],lca=x;
	for(i=x;i!=1;i>>=1){
		now+=vu[i];
		if(now+f[i^1]<mn){
			mn=now+f[i^1];
			mnt=ft[i^1];
			lca=i>>1;
		}
		if(rem[i>>1]&&now<mn){
			mn=now;
			mnt=i>>1;
			lca=i>>1;
		}
	}
	ans+=mn;
	rem[mnt]--;
	for(i=x;i!=lca;i>>=1){
		if(vu[i]==1){
			if(!cd[i]){
				vd[i]=-1;
			}
			cd[i]++;
		}
		if(cu[i]){
			if(!(--cu[i])){
				vu[i]=1;
			}
		}
		
		ud(i);
	}
	for(i=mnt;i!=lca;i>>=1){
		if(vd[i]==1){
			if(!cu[i]){
				vu[i]=-1;
			}
			cu[i]++;
		}
		if(cd[i]){
			if(!(--cd[i])){
				vd[i]=1;
			}
		}
		ud(i);
	}
	for(i=lca;i;i>>=1){
		ud(i);
	}
}
int main(){
	int i,x;
	memset(f,0x3f,sizeof(f));
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++){
		scanf("%d",&rem[i]);
	}
	for(i=n;i;i--){
		vu[i]=vd[i]=1;
		ud(i);
	}
	for(i=1;i<=m;i++){
		scanf("%d",&x);
		find(x);
		printf("%lld ",ans);
	}
	printf("\n");
	return 0;
}

/*
4 5
0 1 1 3 
1 2 4 3 2 


*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值