Advertisement Matching
题解
首先,题目的
a
,
b
a,b
a,b匹配显然是一个很常见的贪心。
有结论,设
a
a
a的集合为
A
A
A,如果
∀
S
⊆
A
,
∑
a
i
∈
S
a
i
⩽
∑
i
=
1
m
min
(
b
i
,
∣
S
∣
)
\forall S\subseteq A,\sum_{a_{i}\in S}a_{i}\leqslant \sum_{i=1}^{m}\min(b_{i},|S|)
∀S⊆A,∑ai∈Sai⩽∑i=1mmin(bi,∣S∣),那么一定存在一种匹配的方法,使得该题的要求成立。
这也就是Gale–Ryser theorem,这里有一种通过网络流的证明,我个人是通过类贪心的思想理解的。
证明:
该结论的必要性是显然的,我们只需要证明其的充分性,也就是满足上面结论时一定有解。
具体来说,由于对于相同的 ∣ S ∣ |S| ∣S∣,后面的 ∑ i = 1 m min ( b i , ∣ S ∣ ) \sum_{i=1}^{m}\min(b_{i},|S|) ∑i=1mmin(bi,∣S∣)的值肯定是一定的。
而前面 ∑ a i ∈ S a i \sum_{a_{i}\in S}a_{i} ∑ai∈Sai的最大时肯定是选择 A A A集合中最大的几个数,我们只需要让这些数的和小于我们后面的值就行了。
如果该集合中的最小值,该式不成立,那显然是不行的,我们不妨假定我们去掉最小值时是成立的,现在要将这个最小值加进去。
我们可以将原来的图形根据 min ( b i , ∣ S ∣ ) \min(b_{i},|S|) min(bi,∣S∣)想象成柱状图,我们现在新加入一个数,肯定有几个柱子会被拔高,而我们的限制是一个柱子最多只能选择一个位置染色。
如果最开始的柱状图就空出一行及以上,如果新加入的 a i ⩽ m a_{i}\leqslant m ai⩽m,显然是有位置的,如果 a i > m a_{i}> m ai>m,之前的肯定也比 m m m大,那之前就判无解了。
否则,如果没有空出一行以上,而我们让延伸的延伸,此时的空位个数肯定不会小于 a i a_{i} ai,如果,能填满就填,不能填满,肯定是有新延伸的空位原来也是空位。
注意,现在还在延伸的位置,之前肯定也是在延伸的,故之前必然是有一个 a i a_{i} ai覆盖了没有延伸的位置,却没有覆盖新延伸的位置,我们可以让它覆盖的位置改变一下,将覆盖没有延伸位置的地方改成覆盖延伸了的为的地方。
由于之前的 a a a都不小于 a i a_{i} ai,所以我们一定可以通过这种方法调整到让 a i a_{i} ai有位置可填。
这样就可以证明该式的充分性了。
证明了上面的结论,我们自然也就想到了如何维护原题是否可行的方法。
我们可以先将
a
a
a值排序,保证我们选择的
S
S
S大小固定时,我们所选择的
a
a
a的和是最大的。
此时,如果
∀
k
∈
[
1
,
n
]
,
∑
i
=
1
m
min
(
b
i
,
k
)
−
∑
i
=
1
k
a
i
⩾
0
\forall k\in[1,n],\sum_{i=1}^{m}\min(b_{i},k)-\sum_{i=1}^{k}a_{i}\geqslant 0
∀k∈[1,n],∑i=1mmin(bi,k)−∑i=1kai⩾0成立,那么我们是一定有解的,否则一定无解。
我们显然可以通过线段树维护不同的
k
k
k的值,如果
min
k
=
1
n
(
∑
i
=
1
m
min
(
b
i
,
k
)
−
∑
i
=
1
k
a
i
)
⩾
0
\min_{k=1}^{n}\left(\sum_{i=1}^{m}\min(b_{i},k)-\sum_{i=1}^{k}a_{i}\right)\geqslant 0
mink=1n(∑i=1mmin(bi,k)−∑i=1kai)⩾0,就有解。
如果
b
i
+
1
b_{i}+1
bi+1,显然会让
k
⩾
b
i
+
1
k\geqslant b_{i}+1
k⩾bi+1的部分,上式的值
+
1
+1
+1。
而
b
i
−
1
b_{i}-1
bi−1则会让
k
⩾
b
i
k\geqslant b_{i}
k⩾bi的部分,上式
−
1
-1
−1。
由于我们
b
b
b的加值是取的
min
(
b
i
,
k
)
\min(b_{i},k)
min(bi,k),这是显然的。
而
a
i
a_{i}
ai的改变则涉及到了该元素的
r
k
rk
rk,记
r
k
x
rk_{x}
rkx表示大于等于
x
x
x的数的个数。
如果
a
i
+
1
a_{i}+1
ai+1,那么
k
⩾
r
k
a
i
+
1
+
1
k\geqslant rk_{a_{i}+1}+1
k⩾rkai+1+1的部分,上式值会
+
1
+1
+1,再让
r
k
a
i
+
1
+
+
rk_{a_{i}+1}++
rkai+1++。
而
a
i
−
1
a_{i}-1
ai−1,会让
k
⩾
r
k
a
i
k\geqslant rk_{a_{i}}
k⩾rkai的部分值
−
1
-1
−1,再让
r
k
a
i
−
−
rk_{a_{i}}--
rkai−−。
以上的四种操作都是线段树上区间的修改,我们只用看修改完的的最小值就行了。
时间复杂度 O ( n + m + q log n ) O\left(n+m+q\log\,n\right) O(n+m+qlogn)。
源码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 250005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long LL;
//#define int LL
typedef unsigned long long uLL;
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mo=998244353;
const int inv2=499122177;
const int jzm=2333;
const int n1=50;
const int lim=250000;
const int zero=10000;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-5;
typedef pair<LL,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
template<typename _T>
void print(_T x){putchar('\n');while(x>9){putchar((x%10)|'0');x/=10;}putchar(x|'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1LL)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1LL;}return t;}
int n,m,q,a[MAXN],b[MAXN],c[MAXN],d[MAXN],rk[MAXN];
LL dif[MAXN],val[MAXN];
class SegmentTree{
private:
LL minn[MAXN<<2],lzy[MAXN<<2];
//void pushup(int rt){minn[rt]=min(minn[lson],minn[rson]);}
#define pushup(rt) minn[rt]=min(minn[lson],minn[rson])
void pushdown(int rt){
if(lzy[rt]){
minn[lson]+=lzy[rt];lzy[lson]+=lzy[rt];
minn[rson]+=lzy[rt];lzy[rson]+=lzy[rt];
lzy[rt]=0;
}
}
public:
void build(int rt,int l,int r){
if(l==r){minn[rt]=val[l];return ;}int mid=l+r>>1;
build(lson,l,mid);build(rson,mid+1,r);pushup(rt);
}
void modify(int rt,int l,int r,int al,int ar,LL aw){
if(l>r||l>ar||r<al||al>ar)return ;
if(al<=l&&r<=ar){minn[rt]+=aw;lzy[rt]+=aw;return ;}
int mid=l+r>>1;pushdown(rt);
if(al<=mid)modify(lson,l,mid,al,ar,aw);
if(ar>mid)modify(rson,mid+1,r,al,ar,aw);
pushup(rt);
}
void search(int rt,int l,int r){
if(l==r){printf("%d ",minn[rt]);return ;}
int mid=l+r>>1;pushdown(rt);
search(lson,l,mid);search(rson,mid+1,r);
}
LL ask(){return minn[1];}
bool query(){return minn[1]>=0;}
}T;
signed main(){
read(n);read(m);LL summ=0;
for(int i=1;i<=n;i++)read(a[i]),c[i]=-a[i],rk[a[i]]++;
for(int i=1;i<=m;i++)read(b[i]),d[i]=b[i];
sort(c+1,c+n+1);sort(d+1,d+m+1);
for(int i=lim;i>0;i--)rk[i]+=rk[i+1];
for(int i=1,j=1;i<=n;i++){
while(j<=m&&d[j]<=i)summ+=1ll*d[j],j++;
summ+=1ll*c[i];val[i]=summ+1ll*i*(m-j+1);
}
T.build(1,1,n);read(q);
for(int i=1;i<=q;i++){
int opt,x;read(opt);read(x);
if(opt==1)a[x]++,rk[a[x]]++,T.modify(1,1,n,rk[a[x]],n,-1);
if(opt==2)T.modify(1,1,n,rk[a[x]],n,1),rk[a[x]]--,a[x]--;
if(opt==3)b[x]++,T.modify(1,1,n,b[x],n,1);
if(opt==4)T.modify(1,1,n,b[x],n,-1),b[x]--;
printf("%d\n",T.query());
}
return 0;
}