2020牛客暑期多校训练营(第五场) D Drop Voicing
2020牛客暑期多校训练营(第五场) D Drop Voicing
题目大意:
给你一个p的全排列,有两种操作:
- 选择倒数第二个挪到第一个
- 把第一个挪到最后一个
如果是连续的第一种操作,则只花费1,第二种操作不花费。
问最少的花费使得p的全排列变成一个顺序的排列。
题解:
这种类型的题目,就是要深入挖掘这个两个操作的实际含义,就和这个题目有点像 Codeforces Round #625 (Div. 2, based on Technocup 2020 Final Round F. Reachable Strings)
自己手动变化这两个操作,明白这两个的实际含义这个题目就会变成一个比较简单的问题了。
先看第一个,我一开始只是单纯的以为每一次的花费就是把从倒数第二个开始往前数若干个挪到最前面,再看第二个,我简单的理解为这个是一个圈,所以就是重新定了一个起点,但是这些发现非常的浅显,只要思考了的都可以发现,而这个又不是纯签到,所以自然需要更加深入的挖掘。
接下来应该看看这两个的集合是一个什么操作,第二个操作我把从倒数第二个往前走的若干个挪到了最前面,但是这若干个数是不是也可以挪到最后一个的后面,所以是不是等价于可以把最后一个挪到前面任意两个数之间。
知道这个之后,这个题目就变得简单起来了,因为任意一个数没有花费可以成为最后一个,而最后一个数1花费就可以挪到任意两个数之间,我们要求这个是一个升序,所以我先找到最长的上升子序列,剩下的数就是花费。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int maxn = 1110;
int a[maxn*2],dp[maxn],n,b[maxn];
int solve(){
for (int i = 1;i <= n;i++) dp[i] = inf;
for (int i = 1;i <= n;i++) *lower_bound(dp +1 , dp + n + 1, a[i]) = a[i];
return lower_bound(dp + 1, dp + n + 1, inf) - dp - 1;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) a[i + n] = a[i];
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, solve());
for(int j = 1; j <= n; j++){
b[j] = a[j];
}
for(int j = 1; j <= n; j++){
a[j] = b[j + 1];
}
a[n] = b[1];
}
printf("%d\n", n - ans);
return 0;
}