题目大意
给你一个数列 (a) ,一个集合 (b) , 对于每个(b) 中的元素(x), (a_x) 不能修改,其他都可以修改,问最少多少次可以将(a) 修改为严格单调递增的。如果不存在,输出 (-1) 。
思路
我们先考虑不存在的情况,一定是存在(x,yin b,x < y) ,(a_y-a_x < y-x) , 这样无法在 (a_x,a_y) 中任意修改满足严格单调递增。
那么接下来考虑最小的修改次数。
加入我们没有修改限制呢?
那么就是一个经典的(DP) 问题了。
我们将原数组变形,让(a_x = a_x -x),然后跑一遍最长不下降子序列就行了(用 (nlogn) 的算法),答案就是 (n-)最长不下降子序列长度了。
但是有限制,我们先考虑一下没有限制的代码。
for(int i = 1;i <= n;i++) a[i] -= i;
for(int i = 1;i <= n;i++){
if(e == 0 || a[i] >= l[e]) {
l[++e] = a[i];
}
else{
int p = upper_bound(l+1,l+e+1,a[i])-l;
l[p] = a[i];
}
}//e : l 数组的末尾标号。
这样的代码会在遍历 (a) 数组时不断更新自己的一个假想的答案序列,使它每个值尽可能的小,然后容下一个新的值,使答案最大。
在判过是否满足后,(forall xin b) ,(a_x) 一定也可以在处理后形成一个不下降子序列,我们要保证不修改它们,就要让它们处于这个假想的答案序列中,于是我们可以加上几行:
//lasb ,上一个不能修改的位置在我们当前维护的假想答案序列中的位置。
//ban[i] , a[i] 是否禁止修改。
for(int i = 1;i <= n;i++){
if(e == 0 || a[i] >= l[e]) {
l[++e] = a[i];
if(ban[i]) lasb = e;
}
else{
int p = upper_bound(l+1,l+e+1,a[i])-l;
if(p <= lasb) continue;//如果我们要更改的位置小于上一个不能修改的位置,那么就不能更改假想的答案序列
l[p] = a[i];
if(ban[i]) lasb = p,e = p;//答案序列p后面的位置都不满足条件,因为它们不能大于这个不能修改的位置,因此对我们要的最长不下降子序列不能产生贡献。
}
}
魔改一下这个算法后,就可以(AC) 了。(具体理解见代码,可以结合一下(nlogn)的最长不下降子序列的算法)
Code
#define re(x) read(x)
const int MAXN = 1e6+10;
int n,k;
int a[MAXN],l[MAXN],e,b[MAXN],lasb=0;
bool ban[MAXN];
int main (){
re(n);re(k);
for(int i = 1;i <= n;i++) re(a[i]);
for(int i = 1;i <= k;i++) re(b[i]),ban[b[i]]=1;
sort(b+1,b+k+1);
for(int i = 2;i <= k;i++)
if(a[b[i-1]] - b[i-1] > a[b[i]] - b[i]){
return puts("-1"),0;
}
for(int i = 1;i <= n;i++) a[i] -= i;
for(int i = 1;i <= n;i++){
if(e == 0 || a[i] >= l[e]) {
l[++e] = a[i];
if(ban[i]) lasb = e;
}
else{
int p = upper_bound(l+1,l+e+1,a[i])-l;
if(p <= lasb) continue;
l[p] = a[i];
if(ban[i]) lasb = p,e = p;
}
}
printf("%d",n-e);
return 0;
}