思路
每次操作都是:
- 把值为 \(a_q\) 的数从当前排列中删除;
- 再把它放到排列末尾。
考虑一个数如果被操作了很多次,那么前面的操作其实都会被后面的操作覆盖。
例如一个数在第 \(2\) 次和第 \(5\) 次操作中都出现,那么最终它的位置只由第 \(5\) 次操作决定。
所以对于所有出现过的数,我们只需要关心它们的最后一次出现位置。
最终排列分成两部分
最终排列可以拆成两部分:
- 从来没有被操作过的数;
- 被操作过的数。
对于第一部分,因为它们从来没有被删除,所以它们之间的相对顺序始终不会改变。
因此只需要按照原排列 \(P\) 的顺序输出所有没有被操作过的数即可。
对于第二部分,它们最终会被依次移动到末尾。
如果两个数 \(x,y\) 的最后一次出现位置满足 \(last_x<last_y\),那么 \(x\) 最后一次被移到末尾发生得更早,所以最终 \(x\) 一定在 \(y\) 前面。
因此,被操作过的数应该按照它们最后一次出现的先后顺序排列。
如何求最后一次出现顺序
可以从后往前扫描操作序列 \(a\)。
如果当前的 \(a_i\) 之前没有出现过,就说明当前位置 \(i\) 正是它的最后一次出现位置。
于是把它加入 ans。
因为我们是从后往前扫描,所以得到的顺序正好是最后出现时间从晚到早。
最后把 ans 翻转一次,就变成最后出现时间从早到晚。
代码中:
per (i, q, 1) {
if (!u[a[i]]) {
u[a[i]] = true;
ans.eb (a[i]);
}
}
reverse (ans.begin (), ans.end ());
其中 u[x] 同时表示值 \(x\) 是否在操作中出现过。
最后输出
先按照原排列顺序输出所有没有被操作过的数:
rep (i, 1, n)
if (!u[p[i]]) cout << p[i] << ' ';
再输出按照最后一次出现顺序排列的所有被操作过的数:
for (int i : ans)
cout << i << ' ';
这样就得到了最终排列。
如何想到的
直接模拟删除和插入会比较麻烦,所以可以先考虑一个数被操作多次时会发生什么。
比如一个数先被移动到末尾,之后又再次被移动到末尾,那么第一次移动显然已经没有意义了。
因此自然想到,每个数其实只需要保留它的最后一次操作。
这样问题就变成:
- 没被操作过的数保持原相对顺序;
- 被操作过的数按照最后一次操作的时间排序。
而想得到“最后一次出现”,最方便的方法就是从后往前扫描,第一次遇到某个数时记录下来即可。
代码
#include <bits/stdc++.h>
#define rep(i,j,k) for (int i = (j); i <= (k); i ++)
#define per(i,j,k) for (int i = (j); i >= (k); i --)
#define uint unsigned int
#define ll long long
#define ull unsigned long long
#define db double
#define ldb long double
#define mkp make_pair
#define eb emplace_back
#define e_ emplace
#define ls (p << 1)
#define rs (p << 1 | 1)
//#define int ll
using namespace std;
const int N = 2e5 + 5;
//const int MOD = 998244353;
//const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;
const ll LINF = 4e18;
//int T;
int n, q;
int p[N], a[N];
bool u[N];
signed main () {
//freopen (".in", "r", stdin);
//freopen (".out", "w", stdout);
ios::sync_with_stdio (0);
cin.tie (0);
cin >> n >> q;
rep (i, 1, n) cin >> p[i];
rep (i, 1, q) cin >> a[i];
vector<int> ans;
per (i, q, 1) {
if (! u[a[i]]) {
u[a[i]] = true;
ans.eb (a[i]);
}
}
reverse (ans.begin (), ans.end ());
rep (i, 1, n) if (!u[p[i]]) cout << p[i] << ' ';
for (int i : ans) cout << i << ' ';
return 0;
}
复杂度
操作序列扫描一次,原排列再扫描一次。
时间复杂度为 \(O(N+Q)\)。
浙公网安备 33010602011771号