CF1437E Make It Increasing
题意描述:
给你一个长度为 \(n\) 的序列 \(a\), 和一个大小为 \(m\) 的集合 \(b\), 其中 \(b\) 集合中的位置不能改,问你最少要修改多少次使得 序列 \(a\) 严格递增。,修改后保证 \(a\) 为整数。 无解输出 \(-1\).
数据范围: \(n,m\leq 5\times 10^5, a_i\leq 10^9\)
solution
比较好玩的构造题。
有一个结论:如果 \(i,j\) (\(i<j\)) 都不修改, 则需要满足 \(a_j-a[i] \geq j-i\) 。
因为你要保证 \(i-j\) 这一段区间严格单调递增,因此每一项至少要与前一项差 \(1\), 那么可以推出 \(a_j\) 至少要和 \(a_i\) 差\(j-i\) 个 \(1\) 才能符合条件,转化成数学形式即 \(a_j - a_i\geq j-i\)
有了这个结论我们就很好做了。
首先,我们先考虑一下无解的情况,如果 \(i,j\in b, i < j\) 且 $a[j]-a[i] < j-i $ ,那么肯定是无解的。
接下来考虑怎么求最少的修改次数。
显然 \(b\) 集合会把序列 \(a\) 分成 \(m+1\) 段,我们对每一段求一个最少的修改次数,在相加就是最后的答案。
我们想让修改次数最少,所以这一段区间中的数尽可能多的不被修改。
由结论可得如果 \(a_i\) 和 \(a_j\) 都不被修改,则要满足 \(a_j-a_i \geq j-i\) ,即: \(a_j-j \geq a_i-i\) 。
我们把序列 \(a\) 中每个元素的价值设为 \(a_i-i\), 然后对 \((b[i-1],b[i])\) 这一段区间求一个最长上升子序列的长度就是最多可以保留的数的个数。
但由于 \(b[i],b[i-1]\) 这两个位置不能被修改即必须被选入最长上升子序列中,我们需要对代码魔改一下。
-
初值只有 \(f[b[i-1]] = 1\), 这样每个元素都可以由 \(f[b[i-1]]\) 转移过来,也就等价于 \(b[i-1]\) 一定会被选上。
-
\(b[i]\) 被选上也就是最长上升子序列的长度为 \(f[b[i]]\)。
求最长上升子序列用树状数组优化一下即可。
坑点:树状数组的初值要设为 \(-inf\) 。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 5e5+10;
const int inf = 1e9+10;
int n,m,ans,flag,cnt;
int f[N],a[N],b[N],w[N],tr[N];
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
int lowbit(int x){return x & -x;}
void cover(int x,int val)//还原树状数组
{
for(; x <= N-5; x += lowbit(x)) tr[x] = val;
}
void chenge(int x,int val)
{
for(; x <= N-5; x += lowbit(x)) tr[x] = max(tr[x],val);
}
int query(int x)
{
int res = -inf;
for(; x; x -= lowbit(x)) res = max(res,tr[x]);
return res;
}
int main()
{
n = read(); m = read();
w[0] = -inf; w[n+1] = inf;
b[++cnt] = w[0]; b[++cnt] = w[n+1];
for(int i = 1; i <= n; i++) b[++cnt] = w[i] = read() - i;
sort(b+1,b+cnt+1);
int num = unique(b+1,b+cnt+1)-b-1;
for(int i = 0; i <= n+1; i++) w[i] = lower_bound(b+1,b+num+1,w[i])-b;
for(int i = 1; i <= m; i++)
{
b[i] = read();
if(w[b[i]] < w[b[i-1]]) flag = 1;
}
b[0] = 0; b[m+1] = n+1;
memset(tr,128,sizeof(tr));
if(flag) printf("%d\n",-1);
else
{
for(int i = 1; i <= m+1; i++)//对每一段进行处理
{
f[b[i-1]] = 1;
chenge(w[b[i-1]],1);
for(int j = b[i-1]+1; j <= b[i]; j++)
{
f[j] = query(w[j]) + 1;
chenge(w[j],f[j]);
}
for(int j = b[i-1]; j <= b[i]; j++) cover(w[j],-inf);
ans += b[i] - b[i-1] + 1 - f[b[i]];
}
printf("%d\n",ans);
}
return 0;
}