题解【P3098】[USACO13DEC]The Bessie Shuffle G
发现顺推需要考虑非常多的东西,不妨来试试倒推。设
如果一张牌它现在是第一位(也就是要被淘汰了),那么它上一轮就在第
这个东西可以用倍增来处理,设
这样,对于每一个询问,我们只要知道它要被淘汰时 / 到最后一轮洗牌的开始位置,再往前推,就可以得到它的初始位置。分两种情况讨论:
-
到了第一轮还没有走出洗牌,直接输出它在第一轮的位置。
-
在中间某一轮走出了洗牌,二分它走出的轮数,接着往后一步一步移就行了。
时间复杂度
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rof(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
const int Maxn=1e5;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m,q,P[Maxn+5],Pre[Maxn+5];
int f[Maxn+5][40]; // 倍增数组
inline int Check(int x,int y,int z)
{
if(y==z+1) return 1;
Rof(i,30,0)
if(y>=(1<<i)) x=f[x][i],y-=(1<<i);
return (x==m+1);
}
inline int Count(int x)
{
int Rnd=min(n-m,n-x); // 需要进行的轮数
int l=1,r=Rnd+1,Start=(x<m)?m-x+1:1; // Start:开始位置
while(l<r)
{
int mid=(l+r)/2;
if(Check(Start,mid,Rnd)) r=mid;
else l=mid+1;
}
if(l==Rnd+1) // 没有走出去
{
x=Start;
Rof(i,30,0)
if(Rnd>=(1<<i)) x=f[x][i],Rnd-=(1<<i);
return Pre[x];
}
else return min(n,m+1+Rnd-l); // 走出去了
}
int main()
{
n=read(),m=read(),q=read(),Pre[m+1]=m+1;
For(i,1,m) P[i]=read(),Pre[P[i]]=i;
For(i,1,m+1) f[i][0]=min(m+1,Pre[i]+1);
For(j,1,30)
For(i,1,m+1) f[i][j]=min(m+1,f[f[i][j-1]][j-1]);
while(q--)
{
int x=read();
printf("%d\n",Count(x));
}
return 0;
}