Task
给出大小为n的集合\(S_1\),其中第i个元素为\((a_i,b_i)\),给出大小为m的集合\(S_2\),第i个元素为\((a_i,b_i)\),现在要从集合\(S_2\)中选出元素i与集合\(S_1\)的元素j配对,但是要满足\(a_i\leq a_j\),\(b_i\leq b_j\),然后得到权值\(a_i\times 500+b_i\times 2\),询问满足配对数量尽可能多的情况下,最大的权值之和,\(1 <= N <= 100000,1 <= M <= 100000\),\(a_i\leq 1440,b_i\leq 100\)。
解
注意到这个问题考虑起来过于麻烦,于是需要转换模型,考虑转换为数列模型,不妨将两个集合合并成一个集合,并标记好属于哪个集合,属于\(S_1\)标记为1,否则标记为0,然后统一按照\(b_i\)为关键字从小到大排序,如果\(b_i\)相同则按起被标记的号子大小排序(含义就是属于\(S_1\)要在后面,往后看,你就知道为什么了),记新的数列中第i个元素为\((a_i,b_i,l_i)\)(其中\(l_i\)对应其被标记的数字)。
先考虑如何选得多。
注意到此时对于其中一个\(l_i=1\)的元素,因为b已经从小到大排序,能够与之配对的元素j必然是它的前面的\(l_j=0\),且\(a_j\leq a_i\)的元素,不妨暴力找出这些元素,现在问题是如何选择这些元素与i配对,朴素的感觉告诉我们最大的满足条件的\(a_j\)。
首先证明\(i\)能够选择一个元素与之配对,否则让出前面的元素给后面的不会配对的元素的配对,结果不改变,如果是让后面的一个会配对的元素配对,放弃它的配对的元素,从而可以让一个新的元素能够配对,也不会改变结果,于是i一定可以选择。
然后对于i的选择,对于可以选择\(j,k(a_k<a_j)\),必然对于后面的元素,要么都不能选择,显然此时i选择一个元素是优秀的,要么是k能够选择,j不能选择,i选择了是优秀的,或者是都能选择,此时i选择任意一个都可以,于是选择\(j\)一定优秀。
现在问题是如何查询小于\(a_i\)的最大的\(a_j\),决策集合有增减,显然想到维护单调性,于是一棵平衡树就可以支持查询这个元素(显然用set替代),并且支持快速插入和删除,于是我们可以做到选的多。
现在问题在于如何使结果尽可能大,注意到一个性质,\(a\)带来的价值远大于\(b\),也就是一个b能够带来最大的权值为\(200\),而a增加一个数字,就能增加\(500\),于是只要让a尽可能优秀,结果就会更加优秀了,因此我们寻找尽可能大的\(a_j\)的策略保证了a最大,是非常优秀的。
还存在一个问题,也就是如果\(a_i\)相同如何选择,显然选择\(b_i\)较大的,这个也不难,只要在平衡树再加一个关键字就是按照\(b\)排序,注意此时查询时,对应的b要赋值为无穷大(不理解看代码)。
因此我们就可以\(O(nlog(n))\)解决这个问题,事实上你可以不用平衡树,因为\(a_i\)很小,可以桶排查找这个最大值,于是你可以改成在\(O(na_i)\)解决这个问题,理论时间复杂度很高,但常数很小。
参考代码:
#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
#include <algorithm>
#define il inline
#define ri register
#define ll long long
#define Size 250000
#define intmax 0x7fffffff
using namespace std;
struct san{
int x,y,l;
il bool operator<(const san&a)const{
return x==a.x?y<a.y:x<a.x;
}
}I[Size];
multiset<san>S;
multiset<san>::iterator gzy;
il void read(int&);
il bool comp(const san&,const san&);
int main(){int n,m;
while(scanf("%d%d",&n,&m)!=EOF){S.clear(),memset(I,0,sizeof(I));
for(int i(1);i<=n;++i)read(I[i].x),read(I[i].y),I[i].l=1;
m+=n;for(int i(n+1);i<=m;++i)read(I[i].x),read(I[i].y);
sort(I+1,I+m+1,comp);int tot(0);ll ans(0);
for(int i(1);i<=m;++i){
if(I[i].l){
if(S.empty())continue;
I[i].y=intmax,gzy=S.upper_bound(I[i]);
if(gzy==S.begin())continue;
--gzy,++tot,ans+=gzy->x*500+gzy->y*2;
S.erase(gzy);
}else S.insert(I[i]);
}printf("%d %lld\n",tot,ans);
}
return 0;
}
il bool comp(const san&a,const san&b){
return a.y==b.y?a.l<b.l:a.y<b.y;
}
il void read(int &x){
x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}