Nonsense Time
Time Limit: 14000/14000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 590 Accepted Submission(s): 162
Problem Description
You a given a permutation p1,p2,…,pn of size n. Initially, all elements in p are frozen. There will be n stages that these elements will become available one by one. On stage i, the element pki will become available.
For each i, find the longest increasing subsequence among available elements after the first i stages.
Input
The first line of the input contains an integer T(1≤T≤3), denoting the number of test cases.
In each test case, there is one integer n(1≤n≤50000) in the first line, denoting the size of permutation.
In the second line, there are n distinct integers p1,p2,…,pn(1≤pi≤n), denoting the permutation.
In the third line, there are n distinct integers k1,k2,…,kn(1≤ki≤n), describing each stage.
It is guaranteed that p1,p2,…,pn and k1,k2,…,kn are generated randomly.
Output
For each test case, print a single line containing n integers, where the i-th integer denotes the length of the longest increasing subsequence among available elements after the first i stages.
Sample Input
1
5
2 5 3 1 4
1 4 5 3 2
Sample Output
1 1 2 3 3
题意: 对给出的每组数组,一开始数组元素全部被冻住了,第二行的数组为第一个数组下标,每个表示当前元素下标被解冻,问当前数组中解冻后的数字中的最长上升子序列(注意是对应一开始数组的位置)。
思路: 正向考虑问题,解冻的下标是随机的,正向解题过于复杂,逆向考虑,每次从末尾开始,相当于模拟不断删除操作和查询操作,为了更快的获取答案,对于每次的最长上升子序列,记录其数组元素下标,如果删除的不是原最长上升子序列的元素,直接标记模拟删除操作,否则重新查找最长上升子序列并标记。详情看代码及注释。
这里的代码采用的是二分找最长上升子序列,若不太清楚二分查找最长上升子序列的,可以参考下一些大佬的博客。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3fffffff;
const int N = 5e4 + 10;
int dp[N], a[N], pos[N];
// dp存最长上升最序列元素,a为原来数组元素,pos为冻结数组下标数组
int n;
int book[N], tot[N], ans[N];
// book标记冻结的数组是否为原最长上升子序列的关键点,tot存最长子序列长度,ans存结果
void find() {
fill(dp, dp + n, inf);
fill(tot, tot + n, -1);
fill(book, book + n, 0);
int maxl = 0;
for (int i = 0; i < n; i++) {
if (a[i] != inf) { // 该数字未被冻结
int pos = lower_bound(dp, dp + n, a[i]) - dp;
if (dp[pos] == inf) { // 如果该位置比当前最长子序列还大,更新最长子序列长度
tot[i] = pos; // 记录下标
dp[pos] = a[i]; // 记录数字
maxl = max(maxl, pos); // 更新最大子序列长度
} else {
tot[i] = pos;
dp[pos] = a[i];
}
}
}
for (int i = n - 1; i >= 0; i--) { // 从下标数组中找当前最长子序列的元素位置
if (maxl < 0) break;
if (tot[i] == maxl) {
book[i] = 1; // 标记当前最长子序列的位置
maxl--;
}
}
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
fill(dp, dp + n, inf);
find(); // 先求最开始的最长上升子序列
for (int i = 0; i < n; i++) {
scanf("%d", &pos[i]);
pos[i]--; // 下标从0开始,对应减1
}
for (int i = n - 1; i >= 0; i--) {
ans[i] = lower_bound(dp, dp + n, inf) - dp; // 找出当前序列的最长上升子序列长度
if (book[pos[i]] == 1) { // 如果该位置是原最长上升子序列的关键点,则重新查找
a[pos[i]] = inf; // 标记该位置数字已被冻结
find(); // 重新查找最长上升子序列
} else { // 如果该位置不是是原最长上升子序列的关键点,则直接冻结该数字
a[pos[i]] = inf;
}
}
for (int i = 0; i < n; i++) {
if (i == n-1) printf("%d\n", ans[i]);
else printf("%d ", ans[i]);
}
}
return 0;
}