【bzoj5326】[Jsoi2017]博弈【模拟费用流】【线段树】

真的就是个位数AC的题目了。。
题目链接
题意:有两个人 A B AB AB,轮流取n个格子,每个格子有两个属性 a [ i ] , b [ i ] a[i],b[i] a[i],b[i] A A A取一个格子 i i i可以获得 a [ i ] a[i] a[i]的收益, B B B i i i可以获得 b [ i ] b[i] b[i]的收益,每个格子只能被取一次, A A A先手。 A A A会根据 B B B的取法选择最优策略,使得自己选的格子的收益和减 B B B的收益和最大, B B B只会贪心地选择一个 b [ i ] b[i] b[i]最大的格子去取。现在有 Q Q Q次修改,每次修改一个格子 i i i a [ i ] a[i] a[i],你需要求出每次修改前后 A A A B B B的收益差。
题解:
B B B的作用就是钦定一个取的顺序。我们把每个格子按照 b [ i ] b[i] b[i]从大到小排序,那么 B B B每次肯定都会取最靠前的一个没被取的格子。因此对于任意的 i i i,前 i i i个格子 A A A最多可以取 ⌈ i 2 ⌉ \lceil \frac{i}{2}\rceil 2i个。
我们设每个格子的权值 w [ i ] = a [ i ] + b [ i ] w[i]=a[i]+b[i] w[i]=a[i]+b[i],那么答案为 ∑ A 选 的 格 子 i w [ i ] − ∑ 1 ≤ i ≤ n b [ i ] \sum_{A选的格子i}w[i]-\sum_{1\leq i \leq n}b[i] Aiw[i]1inb[i]
所以我们问题转化为了,选若干个格子,前 i i i个格子最多可以取 ⌈ i 2 ⌉ \lceil \frac{i}{2}\rceil 2i个,使得它们的权值和最大。
不妨考虑一个最大费用最大流的建模。
S − > i : ( 1 , w [ i ] ) S->i:(1,w[i]) S>i:(1,w[i])
i − > i + 1 : ( ⌈ i 2 ⌉ , 0 ) i->i+1:(\lceil \frac{i}{2}\rceil,0) i>i+1:(2i,0)
n − > T : ( ⌈ n 2 ⌉ , 0 ) n->T:(\lceil \frac{n}{2}\rceil,0) n>T:(2n,0)
这样我们就可以限制前 i i i个格子最多选 ⌈ i 2 ⌉ \lceil \frac{i}{2}\rceil 2i个,最大的费用就是可以得到的权值和的最大值。
但是费用流的复杂度过高,显然不能每次修改后都跑一次求答案。这个费用流建模在一个序列上,比较特殊,所以可以考虑模拟费用流。
不妨设 f l o w i flow_i flowi i − > i + 1 i->i+1 i>i+1,当 i = n i=n i=n时为 n − > t n->t n>t这条边的容量减已流的流量。
考虑每次增广的实质,就是找到一个后缀 i i i,使得 S − > i S->i S>i这条边无流量,对于任意的 j ≤ i ≤ n j\leq i \leq n jin f l o w j > 0 flow_j > 0 flowj>0,且 w [ i ] w[i] w[i]最大。
所以我们可以通过使用支持区间加法的线段树维护区间中 f l o w i flow_i flowi的最小值,以及所有 S − > i S->i S>i无流量的 w [ i ] w[i] w[i]最大值和最大值的位置。
当我们要需要进行一次增广时,我们在线段树上二分找到一个最长的后缀 i i i,使得 i i i T T T这一段的 f l o w i flow_i flowi最小值大于 0 0 0。继续在线段树上查询 [ i , n ] [i,n] [i,n]的满足条件的 w [ i ] w[i] w[i]值最大的位置 j j j,然后我们接下来就要增广 S − > j − > T S->j->T S>j>T这一段,在线段树上把 j − > T j->T j>T路径上的所有 x x x f l o w x flow_x flowx减一。把全局答案加上 w [ i ] w[i] w[i]
看到这里,你可能为疑惑,怎么处理退流?
答案是不需要处理,因为不会退流。
显然,从 S S S连出去的边肯定不会被退流。考虑 S − > T S->T S>T的一段增广路,如果退流了,那么一定走了反向边,那么存在一个 j < i j<i j<i,增广路的形式为 S − > i − > j − > i − > T S->i->j->i->T S>i>j>i>T。但是,因为我们模型的不与 S S S相连的所有边的费用都为 0 0 0,所以这样的一条增广路其实等价于 S − > i − > T S->i->T S>i>T,不需要考虑退流。
然后我们只需要不停地执行上面这个增广的操作,直到找不到增广路,就可以求出最大费用最大流。
现在我们考虑修改操作。
修改操作相当于删除一条边和加入一条边,边的形式都为 S − > i S->i S>i,所以我们分成两种操作来考虑。
1. 1. 1.删除一条 S − > i S->i S>i的边。
如果这条边没有流量,不用处理。
如果有流量,那么只需要把 i − > T i->T i>T上的所有点 j j j f l o w j flow_j flowj全部 + 1 +1 +1,在线段树上区间加。然后全局答案减去 w [ i ] w[i] w[i]。然后进行一次增广。
为什么只用进行一次增广?
如果在图上可以找到多条增广路,在删去这条边之前也应该还可以继续增广,与这张图原来就已经求出了最大费用最大流矛盾。
2. 2. 2.加入一条 S − > i S->i S>i的边。
我们先直接把这条边加入图中,然后进行一次增广。
但是这样就够了吗?
不够,有可能我将某一条增广路退流,再从这条边开始增广,得到的答案更优。
但是不是说不用考虑退流吗?
注意,那是建立每次取最长路的基础上的结论。现在我们增广的顺序被打乱了,所以可能需要考虑替换一条增广路。
我们考虑退流一条 S − > j − > T S->j->T S>j>T的增广路,再加入一条 S − > i − > T S->i->T S>i>T的增广路,需要满足什么条件。
因为 S − > i − > T S->i->T S>i>T的一条增广路不能直接增广,所以 m i n [ f l o w i , f l o w i + 1 , . . f l o w n ] = 0 min [flow_i,flow_{i+1},..flow_{n} ]=0 min[flowi,flowi+1,..flown]=0
我们找到一个 k k k,使得 m i n [ f l o w i , . . . f l o w k − 1 ] > 0 min[flow_i,...flow_{k-1}]>0 min[flowi,...flowk1]>0 f l o w k = 0 flow_k=0 flowk=0。则 j j j必须满足 j ≤ k j\leq k jk,才能使得退流 S − > j − > T S->j->T S>j>T的增广路后, S − > i − > T S->i->T S>i>T可以增广。 k k k可以直接在线段树上二分求出,每次的复杂度为 O ( l o g 2 2 n ) O(log_2^2n) O(log22n)
显然删去的增广路的费用越小越优,所以我们只需要选择 1 ≤ j ≤ k 1\leq j \leq k 1jk当中, S − > j S->j S>j有流量且 w [ j ] w[j] w[j]最小的 j j j。所以我们需要在线段树上维护 S − > i S->i S>i有流量且 w [ i ] w[i] w[i]最小的 i i i,然后直接在线段树上查询即可。如果 w [ j ] < w [ i ] w[j]<w[i] w[j]<w[i],那么把 S − > j − > T S->j->T S>j>T退流再增广 S − > i − > T S->i->T S>i>T一定更优,我们就把 f l o w x , j ≤ x ≤ n flow_{x},j\leq x \leq n flowx,jxn全部 + 1 +1 +1,再 f l o w x , i ≤ x ≤ n flow_{x},i\leq x \leq n flowx,ixn全部 − 1 -1 1,在线段树上维护 f l o w flow flow。把全局答案加上 w [ i ] − w [ j ] w[i]-w[j] w[i]w[j]
这样,我们就使用模拟费用流,在 O ( n l o g 2 2 n ) O(nlog_2^2n) O(nlog22n)的时间复杂度内解决了这一题。
orz myh 讲的做法
orz scb,他指出了许多问题,而且发现模拟费用流的做法和Claris的贪心做法本质相同。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cassert>
using namespace std;
typedef long long ll;
const int N=100005;
int n,q,x,v,bel[N],minn[N*4],tag[N*4];
pair<int,int> maxv[N*4],minv[N*4];
bool use[N];
ll s,ans;
struct data{
	int a,b,id;
}a[N];
bool cmp(data a,data b){
	return a.b==b.b?a.id<b.id:a.b>b.b;
}
void pushdown(int o){
	if(tag[o]){
		tag[o*2]+=tag[o];
		minn[o*2]+=tag[o];
		tag[o*2+1]+=tag[o];
		minn[o*2+1]+=tag[o];
		tag[o]=0;
	}
}
void pushup(int o){
	minn[o]=min(minn[o*2],minn[o*2+1]);
	maxv[o]=max(maxv[o*2],maxv[o*2+1]);
	minv[o]=min(minv[o*2],minv[o*2+1]);
}
void build(int o,int l,int r){
	if(l==r){
		minn[o]=(l+1)/2;
		maxv[o]=make_pair((a[l].a+a[l].b)*(1-use[l]),l);
		minv[o]=make_pair(use[l]?a[l].a+a[l].b:0x7fffffff,l);
		return;
	}
	int mid=(l+r)/2;
	build(o*2,l,mid);
	build(o*2+1,mid+1,r);
	pushup(o);
}
void update(int o,int l,int r,int k){
	if(l==r){
		maxv[o]=make_pair((a[l].a+a[l].b)*(1-use[l]),l);
		minv[o]=make_pair(use[l]?a[l].a+a[l].b:0x7fffffff,l);
		return;
	}
	pushdown(o);
	int mid=(l+r)/2;
	if(k<=mid){
		update(o*2,l,mid,k);
	}else{
		update(o*2+1,mid+1,r,k);
	}
	pushup(o);
}
void update(int o,int l,int r,int L,int R,int v){
	if(L<=l&&R>=r){
		minn[o]+=v;
		tag[o]+=v;
		return;
	}
	pushdown(o);
	int mid=(l+r)/2;
	if(L<=mid){
		update(o*2,l,mid,L,R,v);
	}
	if(R>mid){
		update(o*2+1,mid+1,r,L,R,v);
	}
	pushup(o);
}
pair<int,int> qmax(int o,int l,int r,int L,int R){
	if(L==l&&R==r){
		return maxv[o];
	}
	pushdown(o);
	int mid=(l+r)/2;
	if(R<=mid){
		return qmax(o*2,l,mid,L,R);
	}else if(L>mid){
		return qmax(o*2+1,mid+1,r,L,R);
	}else{
		return max(qmax(o*2,l,mid,L,mid),qmax(o*2+1,mid+1,r,mid+1,R));
	}
}
pair<int,int> qmin(int o,int l,int r,int L,int R){
	if(L==l&&R==r){
		return minv[o];
	}
	pushdown(o);
	int mid=(l+r)/2;
	if(R<=mid){
		return qmin(o*2,l,mid,L,R);
	}else if(L>mid){
		return qmin(o*2+1,mid+1,r,L,R);
	}else{
		return min(qmin(o*2,l,mid,L,mid),qmin(o*2+1,mid+1,r,mid+1,R));
	}
}
int query(int o,int l,int r){
	if(l==r){
		return minn[o]>0?l:l+1;
	}
	pushdown(o);
	int mid=(l+r)/2;
	if(minn[o*2+1]>0){
		return query(o*2,l,mid);
	}else{
		return query(o*2+1,mid+1,r);
	}
}
int query(int o,int l,int r,int L,int R){
	int mid=(l+r)/2;
	if(L<=l&&R>=r){
		if(l==r){
			return minn[o]>0?l:l-1;
		}
		pushdown(o);
		if(minn[o*2]>0){
			return query(o*2+1,mid+1,r,L,R);
		}else{
			return query(o*2,l,mid,L,R);
		}
	}
	pushdown(o);
	int res=-1;
	if(L<=mid){
		res=query(o*2,l,mid,L,R);
	}
	if(R>mid){
		if(res==mid||res==-1){
			res=query(o*2+1,mid+1,r,L,R);
		}
	}
	return res;
}
bool modify(){
	int x=query(1,1,n);
	if(x>n){
		return false;
	}
	pair<int,int> tmp=qmax(1,1,n,x,n);
	if(!tmp.first){
		return false;
	}
	ans+=tmp.first;
	use[tmp.second]=true;
	update(1,1,n,tmp.second);
	update(1,1,n,tmp.second,n,-1);
	return true;
}
void modify(int i){
	int x=query(1,1,n,i,n)+1;
	pair<int,int> tmp=qmin(1,1,n,1,x);
	if(a[i].a+a[i].b>tmp.first){
		ans+=a[i].a+a[i].b-tmp.first;
		use[tmp.second]=false;
		update(1,1,n,tmp.second);
		update(1,1,n,tmp.second,n,1);
		use[i]=true;
		update(1,1,n,i);
		update(1,1,n,i,n,-1);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i].a);
		a[i].id=i;
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i].b);
		s+=a[i].b;
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
		bel[a[i].id]=i;
	}
	build(1,1,n);
	while(modify());
	printf("%lld\n",ans-s);
	scanf("%d",&q);
	while(q--){
		scanf("%d%d",&x,&v);
		x=bel[x];
		if(use[x]){
			ans-=a[x].a+a[x].b;
			update(1,1,n,x,n,1);
			modify();
			use[x]=false;
		}
		a[x].a=v;
		update(1,1,n,x);
		modify();
		if(!use[x]){
			modify(x);
		}
		printf("%lld\n",ans-s);
	}
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值