题解:P3098 [USACO13DEC] The Bessie Shuffle G
a_cow_of_FJ · · 题解
来一发线性复杂度的做法。
思路
首先感性理解一下洗牌过程。
发现就是一个大小为
若以这个滑动窗口为参照物,可以理解为是整个牌堆在向上移动。那么窗口内的第
对于原牌堆中的第
设这个轮数为
意思时窗口底部本来位于
解方程得
但是,上述结论仅适用于从窗口底部进入,从顶部离开的牌,所以编号为
于是下面考虑前
先考虑前者。对于窗口内第
但是有个两个问题:
- 万一这张牌永远洗不掉,
d_i=+\infty 时怎么办? - 由于一共只洗
n-m+1 次,万一n-m+1<d_i 怎么办?
这两种情况可以放在一起处理。这种情况下,每次洗牌该牌都会参与,即它会经历共
下面考虑如何处理倒数
到这里为止说的很抽象,可以接着往下看,
实现
在考虑代码之前,先考虑一下如果给置换
显然除了
容易发现,图由一条
考虑把链和环记录到若干个 vector 中,并记 vector 编号,记 vector 中的下标。特别地,我们把链存在
这么做有如下好处:
- 可以用
vec[bel[i]][ pos[i] + x ] 方便地表示i 经过x 轮置换后的位置(前提是不越界)。 - 前文提到的
c 即为vec[1].size()-1 ,d_i 即为vec[1].size() - 1 - pos[i] 。 - 若
bel[i]=1 则点i 在链上,否则在环上,且vec[bel[i]].size() 即为循环节。
下面贴高清无注释的代码:
参考代码
#include <iostream>
#include <vector>
using namespace std;
const int M = 1e5 + 5;
int n, m, pre[M], bel[M], pos[M], siz[M], cur;
vector<int> vec[M];
int res1[M], res2[M];
void prep() {
for (int i = 0; i <= m; i++)
if (!bel[i]) {
vec[++cur].push_back(i);
bel[i] = cur;
for (int j = pre[i]; j && !bel[j]; j = pre[j]) {
vec[cur].push_back(j);
bel[j] = cur, pos[j] = vec[cur].size() - 1;
}
}
for (int i = 1; i <= cur; i++) {
siz[i] = vec[i].size();
if (i > 1) {
for (int j = 0; j < siz[i]; j++)
vec[i].push_back(vec[i][j]), pos[ vec[i][j] ] += siz[i];
}
}
for (int i = 1; i < m; i++) {
if (bel[i] == 1 && pos[i] <= n - m + 1)
res1[ pos[i] ] = i;
else {
int r = (n - m + 1) % siz[ bel[i] ];
res2[ m - vec[ bel[i] ][ pos[i] - r ] ] = i;
}
if (min(i, n - m + 1) <= siz[1] - 1)
res2[ m - vec[1][ siz[1] - 1 - min(i, n - m + 1) ] ] = n - i + 1;
}
}
int query(int x) {
if (x > n - m + 1 && res1[n - x + 1])
return res1[n - x + 1];
if (x < m)
return res2[x];
return n + m + 1 - siz[1] + 1 - x;
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int q;
cin >> n >> m >> q;
for (int i = 1, x; i <= m; i++) {
cin >> x;
pre[x - 1] = i;
}
prep();
while (q--) {
int x;
cin >> x;
cout << query(x) << '\n';
}
}
如果给这份代码加个快读就变成了次优解(至少目前是这样)。
完结撒花~~