真的就是个位数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
#include
#include
#include
#include
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;
}