题380.ABC260-D - Draw Your Cards
一、题目
二、题解
本题题意为每次递出一张写着X号的牌,将这张牌放到桌上,如果桌上有某叠牌处在最上面的牌上写的号比X来的大,则将递来的牌盖在那叠牌的上面,否则则将牌放在桌子的其他地方,即新开一叠牌。
依上述说法,我们可以想着用set来维护桌子上的每叠牌。因为每叠牌被看到的只有最上面那张,所以每叠牌在set里只用那最上面的牌来表示。利用set递增排序的性质,我们按题意每次递牌的时候可用lower_bound二分去找最小的比X大或者等于的牌号,然后做盖住操作(set删除那个牌号并插入新牌),如果找不到则直接插入set。对此上述与pta上的一题列车调度类似。
除此之外,由于本题还要求每叠牌数目达到K时要被整叠eat,因此我们还必须开一个数组去维护某张牌对应的那叠牌的数目,每次在做set操作的同时去对这个数组做更新。另外,还需要一个res数组来存每号牌被eat的次序为第几。
最后,由于只直接实现上述操作会超时,所以还需要加一个并查集来对res做操作。
详细思路可见代码注释,具体代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+2;
int N,K;
set<int> s;
//cnt[i]表示写着i号的卡牌包括它下面有多少张卡片。res[i]<0表示i为根牌,它的相反数为它以及它下面的所有卡片的被eat掉的次序为第几,res[i]>0表示i的祖先牌为谁
int cnt[maxn],res[maxn];//初始res[i]都为0,因为我们这里用0表示还没有任何次序,即没被eat
int findRoot(int v)//并查集(带路径压缩
{
if(res[v]<=0) return v;//当res[v]<=0时,写着v号的牌为根牌
else return res[v]=findRoot(res[v]);
}
int main()
{
scanf("%d%d",&N,&K);
for(int i=1;i<=N;i++)
{
int p;
scanf("%d",&p);
auto it=s.lower_bound(p);//在set中用二分去找最小的大于等于p的数
if(it!=s.end())//在set里找到了>=p的第一个数(必定是最小的那个>=p的数,因为是递增set)
{
int t=*it;
cnt[p]=cnt[t]+1;//p此时放到了t的上面,盖住t,cnt[p]继承cnt[t],并+1
res[t]=p;//p作为t的祖先牌
s.erase(it);//t被p盖住所以没用了,后续看桌子上那叠牌也是看到p,所以把t从set删了
}
else cnt[p]=1;//如果找不到,则自己独立做一叠牌放在桌上
s.insert(p);//将p牌入set
if(cnt[p]==K)//当p牌所在的那叠牌数目等于K时,就将那叠牌eat掉,并赋予res
{
s.erase(p);
res[p]=-i;//常见思想,p作为并查集根节点,以res为负数作为答案,表示第几个被eat
}
}
for(int i=1;i<=N;i++)
{
int root=findRoot(i);//对于每个号的牌,我们先去找它的根
if(!res[root]) printf("-1\n");//如果res=0则没有被eat,则输出-1
else printf("%d\n",-res[root]);//反之输出它的相反数
}
}