真的就是个位数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]
∑A选的格子iw[i]−∑1≤i≤nb[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
j≤i≤n,
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,...flowk−1]>0,
f
l
o
w
k
=
0
flow_k=0
flowk=0。则
j
j
j必须满足
j
≤
k
j\leq k
j≤k,才能使得退流
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
1≤j≤k当中,
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,j≤x≤n全部
+
1
+1
+1,再
f
l
o
w
x
,
i
≤
x
≤
n
flow_{x},i\leq x \leq n
flowx,i≤x≤n全部
−
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;
}