P3098
题目。
首先考虑 \(O(NQ)\) 暴力。
发现求 \(B\) 的顶部开始第 \(q_i\) 张牌并不好求,我们可以先求一开始编号为 \(i\) 的卡片在 \(B\) 的位置绝对不是我一开始看错题了。
按以下规则跳:
\(1\):位置 \(i\) 在第 \(x\) 次洗牌后到了 \(p_{i-x+1}+x-1\)(\(1\le i-x+1\le m\)),有些位置在第 \(x\) 次洗牌是不变的。
\(2\):第 \(x\) 次洗牌后位于 \(x\) 位置的被选出。
于是可以写出代码,注意代码中 \(x\) 表示初始位置,\(now\) 表示开始第一次洗牌是全局的第几次洗牌。
int now=max(0,x-m),pos=x;
while(now!=pos&&now<n-m+1){
now++;
pos=p[pos-now+1]+now-1;
}
接下来求 \(B\) 的顶部开始第 \(q_i\) 张牌。
这相当于求第 \(n-q_i+1\) 个被选出的卡片的编号。
这其实就是倒着做而已。
初始条件为 \(now = min(x, n - m + 1), pos = x;\),注意这里的 \(x\) 指第 \(x\) 个被选出。
设 \(pp_{p_{i}}=i\),反推得到。
while(now){
pos=pp[pos-now+1]+now-1;
now--;
if(pos>now+m-1)break;
}
注意一下如果不再被洗牌的话要跳过。
这样就可以拿到 50pts。
一次一次跳还是太慢了,考虑加速。
计 \(now_i,pos_i\) 表示跳第 \(i\) 次的值。
\[\begin{cases}
now_i= \min (x,n-m+1),pos_i=x,i=0\\
now_i=now_{i-1}-1,pos_i=pp_{pos_{i-1}-now_{i-1}+1}+now_{i-1}-1,i\ge 1
\end{cases}
\]
多推几下。
\[\begin{aligned}
pos_{i}&=pp_{pos_{i-1}-now_{i-1}+1}+now_{i-1}-1\\
&=pp_{pp_{pos_{i-2}-now_{i-2}+1}+now_{i-2}-1-now_{i-1}+1}+now_{i-2}-1-1\\
&=pp_{pp_{pos_{i-2}-now_{i-2}+1}+1}+1+now_{i-2}-3\\
&=pp_{pp_{pp_{pos_{i-3}-now_{i-3}+1} +1}+1}+1+now_{i-3}-4\\
&=\dots
\end{aligned}
\]
注意到形式一致的,于是考虑把 \(i\) 向 \(pp_i+1\) 连边,然后跳边可以直接倍增。
注意跳的话要满足:
\(1\):最多跳 \(now_0\) 步。
\(2\):跳到不合法停止(\(fa\) 为 \(0\))。
一开始我不知道该跳多少步,然后二分了一下虽然还是过了,但其实没必要,直接贪心即可。
最后时间复杂度 \(O((M+Q)\log N)\)。
所以 \(Q\) 开这么小 hyw 啊?
code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,r,l) for(int i=(r);i>=(l);--i)
const int N=1e5+5;
int n,m,Q,x;
int p[N],pp[N],f[N][35];
void solve(){
cin>>n>>m>>Q;
rep(i,1,m) cin>>p[i],pp[p[i]]=i;
rep(i,1,m) f[i][0]=pp[i]+1;
rep(j,1,30) rep(i,1,m) f[i][j]=f[f[i][j-1]][j-1];
while(Q--){
cin>>x;
x=n-x+1;
int now=min(x,n-m+1),pos=x,ans=0;
int st=pos-now+1;
per(j,30,0) if(ans+(1<<j)<=now&&f[st][j]) st=f[st][j],ans+=(1<<j);
cout<<st+now-(ans+1)<<'\n';
}
}
int main(){
cin.tie(0)->ios::sync_with_stdio(false);
solve();
}

浙公网安备 33010602011771号