P3988 [SHOI2013] 发牌
P3988 [SHOI2013] 发牌
题目描述
在一些扑克游戏里,如德州扑克,发牌是有讲究的。一般称呼专业的发牌手为荷官。荷官在发牌前,先要销牌(burn card)。所谓销牌,就是把当前在牌库顶的那一张牌移动到牌库底,它用来防止玩家猜牌而影响游戏。
假设一开始,荷官拿出了一副新牌,这副牌有 \(N\) 张不同的牌,编号依次为 \(1\) 到 \(N\)。由于是新牌,所以牌是按照顺序排好的,从牌库顶开始,依次为 \(1,2,\cdots,N\),\(N\) 号牌在牌库底。为了发完所有的牌,荷官会进行 \(N\) 次发牌操作,在第 \(i\) 次发牌之前,他会连续进行 \(R_i\) 次销牌操作,\(R_i\) 由输入给定。请问最后玩家拿到这副牌的顺序是什么样的?
数据规模与约定
对于 \(100 \%\) 的数据,\(0 \le R_i < N\) , \(N\le7\times 10^5\)。
说句闲话:

年底平衡树大甩卖,比模板还简单的平衡树紫题仅需40行,走过路过不要错过!
Solution:
言归正转,我们只需要搞清楚这题的洗牌方式就能愉快的把这题A掉。
以初始序列为例,假设我们要进行 \(R\) 次销牌操作:
第0次: \(1,2,3...N\)
第1次: \(2,3...N,1\)
第2次: \(2...N,1,2\)
第 \(R\) 次 :\(R+1...N,1,2...R\)
所以“连续进行 \(R\) 次销牌操作就相当于把 \([1,R]\) 这个区间取出来然后拼到 \(N\) 的后面。那么我们取到的就会是 \(R+1\) 这张牌”
但是要注意的是,由于我们牌的序列是动态的,也就是说,我上述所有的标号是其实当前的牌堆序列的下标,而不是最初的标号。所以我并不认为线段树好写,所以我选了FHQ。
Code:
#include<bits/stdc++.h>
const int N=7e5+5;
using namespace std;
int n,m,cnt,rt;
struct Tree{
int ls,rs,val,siz,rev,pri;
}t[N];
inline int rd(){return rand()*rand()+17;}
inline int Node(int val){t[++cnt]={0,0,val,1,0,rd()};return cnt;}
inline void pushup(int x){t[x].siz=t[t[x].ls].siz+t[t[x].rs].siz+1;}
inline void splite(int x,int &a,int &b,int k)
{
if(!x){a=b=0;return;}int tmp=t[t[x].ls].siz+1;
if(k>=tmp){a=x;splite(t[x].rs,t[x].rs,b,k-tmp);}
else {b=x;splite(t[x].ls,a,t[x].ls,k);}
pushup(x);
}
int merge(int x,int y)
{
if(!x||!y)return x|y;
if(t[x].pri<t[y].pri){t[x].rs=merge(t[x].rs,y);pushup(x);return x;}
else {t[y].ls=merge(x,t[y].ls);pushup(y);return y;}
}
void work()
{
cin>>n;
for(int i=1;i<=n;i++){rt=merge(rt,Node(i));}
for(int i=1,x,a,b,c;i<=n;i++)
{
scanf("%d",&x);x=x%t[rt].siz+1;
splite(rt,a,b,x-1);splite(b,b,c,1);
printf("%d\n",t[b].val);
rt=merge(c,a);
}
}
int main()
{
work();
return 0;
}

浙公网安备 33010602011771号