题目链接
题意:给一个长度为n的序列A,通过改变其中的一些位置上的值来使得序列单调严格上升
n
≤
35000
n \le 35000
n≤35000
求最小的修改位置个数 和 最小修改个数前提下的最小修改绝对值之和
据说这种做法叫补集转化?
最少修改个数 -> n - 最大不修改个数
而对于一个位置pos 他可以不修改
那么对于它前面的所有点 一定有
a
i
+
p
o
s
−
i
≤
a
p
o
s
a_i + pos - i \le a_{pos}
ai+pos−i≤apos
所以就有了DP方程
f
p
o
s
=
max
i
=
1
p
o
s
−
1
f
i
+
1
(
a
i
+
p
o
s
−
i
≤
a
p
o
s
)
f_{pos} = \max_{i = 1}^{pos - 1} f_i + 1 (a_i + pos - i \le a_{pos})
fpos=i=1maxpos−1fi+1(ai+pos−i≤apos)
那么答案就是
max
i
=
1
n
f
i
\max_{i = 1}^{n} f_i
i=1maxnfi
然鹅这是
O
(
n
2
)
O(n^2)
O(n2)的复杂度啊?!
可以看到 限制我们一个一个判的就是那个
a
i
+
p
o
s
−
i
≤
a
p
o
s
a_i + pos - i \le a_{pos}
ai+pos−i≤apos
来推一下这个式子
a
i
+
p
o
s
−
i
≤
a
p
o
s
a_i + pos - i \le a_{pos}
ai+pos−i≤apos
=
a
i
−
i
≤
a
p
o
s
−
p
o
s
a_i - i \le a_{pos} - pos
ai−i≤apos−pos
那如果构造一个新的序列使得
B
i
=
A
i
−
i
B_i = A_i - i
Bi=Ai−i
再加上这个转移方程。。。这分明是最长不下降子序列鸭
第一问完成了来看第二问
我太菜了不是很能说明白。。
这里要参考一下这位大佬的题解
传送门
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 35005;
const int inf = 0x3f3f3f3f;
struct Edge{
int u, v, next;
}edge[N << 1];
int head[N], esize;
inline void addedge(int x, int y){
edge[++esize] = (Edge){x, y, head[x]};
head[x] = esize;
}
int n;
int a[N], b[N], lis[N], lislen, len[N];
long long sl[N], sr[N], f[N];
int main() {
memset(head, -1, sizeof(head));
scanf("%d", &n);
for(int i = 1; i <= n; ++i){
scanf("%d", &a[i]); b[i] = a[i] - i;
}
for(int i = 1, l, r, mid; i <= n; ++i){
l = 0, r = lislen;
while(l < r){
mid = l + ((r - l + 1) >> 1);
if(lis[mid] <= b[i]) l = mid;
else r = mid - 1;
}
if(l == lislen) ++lislen;
lis[l + 1] = b[i];
len[i] = l + 1;
addedge(l + 1, i);
}
addedge(0, 0);//!
printf("%d\n", n - lislen);
memset(f, 0x3f, sizeof(f));
len[n + 1] = lislen + 1;
b[0] = -inf; b[n + 1] = inf;
f[0] = 0;
for(int i = 1; i <= n + 1; ++i){
//printf("%d %d\n", len[i] - 1, head[len[i] - 1]);
for(int j = head[len[i] - 1], vv; ~j; j = edge[j].next){
vv = edge[j].v;
if(vv > i || b[vv] > b[i]) continue;
sl[vv] = sr[i] = 0;
for(int k = vv + 1; k <= i; ++k) sl[k] = sl[k - 1] + abs(b[k] - b[vv]);
for(int k = i - 1; k >= vv; --k) sr[k] = sr[k + 1] + abs(b[k] - b[i]);
for(int k = vv; k <= i - 1; ++k){
//printf("update i%d vv%d sl%lld sr%lld f%lld\n", i, vv, sl[k], sr[k + 1], f[vv]);
f[i] = min(f[i], f[vv] + sl[k] + sr[k + 1]);
}
}
}
printf("%lld", f[n + 1]);
return 0;
}
```