题解:P3696 Bushiroad的偶像派对
Hog_Dawa_IOI · · 题解
思路来自同机房大巨佬,膜拜。
转化一下题意,有集合
考虑贪心,能不改变的就先不改变。
但是如果实在匹配不到必须要修改的时候,我们需要快速找到别的集合中没有被匹配到的数字进行匹配。
因此我们使用一棵值域线段树(事先离散化,也可以动态开点),来记录所有待匹配的值。
每一次我们先在当前集合中寻找可以匹配的值,如果可以匹配那么把它从线段树中删除。否则答案
这个标记什么意思呢?就是说我这里有一个数字,它需要被匹配。我们不知道它和谁匹配,但反正它需要被匹配。
这样有一个好处,就是线段树中的前缀和代表了当前等待着被匹配的数的数量。如果当前前缀和
于是我们转化一下,用值域线段树维护当前的前缀和。
首先把所有“中场值”塞进线段树中。
比如样例,离散化后是
3
3 4
2 2
1 1
1 6
3 5
3 3
那么我们把
同时在标号对应的 set 中记录。
然后遍历
在查询之前,我们需要处理掉标号对应 set 中一些不可以参加匹配的点。什么意思呢?就是由于先前打上了
然后找到小于等于
这样就做完了。注意离散化数组开了
有一些细节在注释中。
#include<set>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,lsh[400005],tot,ans;
int typa[200005],sa[200005];
int typb[200005],sb[200005];
multiset<int>num[200005];
long long mn[1600005],laz[1600005];
void down(int rt)
{
mn[rt<<1]+=laz[rt],
laz[rt<<1]+=laz[rt],
mn[rt<<1|1]+=laz[rt],
laz[rt<<1|1]+=laz[rt],
laz[rt]=0;
}
void add(int rt,int l,int r,int ll,int rr,int num)
{
if(rr<l||r<ll) return;
if(ll<=l&&r<=rr){mn[rt]+=num,laz[rt]+=num;return;}
down(rt),add(rt<<1,l,(l+r)/2,ll,rr,num),
add(rt<<1|1,(l+r)/2+1,r,ll,rr,num);
mn[rt]=min(mn[rt<<1],mn[rt<<1|1]);
}
void cle(int wh)
{
while(!num[wh].empty())
{
int dq=*num[wh].begin();
add(1,1,tot,dq,tot,-1);
if(mn[1]>=0){add(1,1,tot,dq,tot,1);break;}
add(1,1,tot,dq,tot,1);//查询后记得还原回去
num[wh].erase(num[wh].find(dq));
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&typa[i],&sa[i]),lsh[i]=sa[i];
for(int i=1;i<=n;i++) scanf("%d%d",
&typb[i],&sb[i]),lsh[i+n]=sb[i];
sort(lsh+1,lsh+2*n+1),
tot=unique(lsh+1,lsh+2*n+1)-lsh-1;
for(int i=1;i<=n;i++) sa[i]=
lower_bound(lsh+1,lsh+tot+1,sa[i])-lsh,
num[typa[i]].insert(sa[i]),
add(1,1,tot,sa[i],tot,1);
for(int i=n;i>=1;i--)
//由于输入数据从大到小排序,因此要从后往前遍历
{
sb[i]=lower_bound(lsh+1,lsh+
tot+1,sb[i])-lsh;cle(typb[i]);
if(num[typb[i]].empty()||
num[typb[i]].upper_bound(sb[i])
==num[typb[i]].begin())
{ans++,add(1,1,tot,sb[i],tot,-1);continue;}
//在线段树上打-1标记
auto dq=prev(num[typb[i]].upper_bound(sb[i]));
add(1,1,tot,*dq,tot,-1),num[typb[i]].erase(dq);
//用“-1”标记代表从线段树中删除数字,同时从 set 中删除
}printf("%d",ans);
}