题目背景:
分析:DP lis + 黑科技······
有没有人能给我解释下,为什么D1T1也可以这么强大······内心绝望不想说话·······来记录下这道题···
首先,对于第一问,显然对于一个可行的最长上升序列中的任意两项i, j(i > j) 一定满足 a[i] – a[j] >= i – j,移项一下得到a[i] – i >= a[j] – j, 所以我们定义b[i] = a[i] – i,然后求一遍最长不下降子序列即可,本来平时用的树状数组,然而这次有负数,懒得离散化了,就直接用二分了。
然后,对于第二问,我们定义dp[i]表示调整合法原序列1 ~ i的最小代价,然后比较显然的转移方程为
dp[i] = dp[j] + cost(j+ 1, i)
j为满足b[j] <= b[i] 并且 f[j] + 1 == f[i]位置,(f[i]为以i结尾的最长的不下降子序列长度)
对于cost(j + 1, i)如何求取,这里有一个牛逼的结论,在j + 1 ~ i - 1之间,一定存在一个位置t,满足当b[j] ~ b[t]都调整为b[j], b[t + 1] ~ b[i]都调整为b[i]时最优,证明的话······听说可以数学归纳法,我这里就简单说一下自己的理解,首先如果,目前来看,存在一个点b[k]调整为了x, 满足x < b[i], x > b[j], 对于所有k + 1 ~ i的点全部调整为b[i], j ~ k – 1全部调整为b[j], 显然,当满足f[j] + 1 == f[i]的情况下,所有在i ~ j之间的点,一定要么大于b[i]要么小于b[j], (若存在b[m] >= b[j]&& b[m] <= b[i], f[m] >= f[j] + 1, f[i] >= f[m] + 1, f[i]>= f[j] + 2)所以如果当前的b[k] < b[j], b[j] – b[k] < x – b[k], 调整为b[j]更优,若b[k] > b[i],b[k] – b[i] < b[k] – x, 选择调整为b[i]更优,当然这样定性分析应该是不全面的,但是大致感性理解下应该还是比较能接受吧······
好了现在我们已经有一个O(n3)的方法来解决此题了,(枚举i, j, 分割点t),其实听说已经就可以过掉这道题了,但是我们可以来理性愉悦一下,追求下更低的复杂度······
考虑如何优化分割点的求取,首先对于i, j(j < i && b[j] <= b[i]&& f[j] + 1 == f[i])假设我们当前将所有的b[j + 1 ~ i]全部修改为b[i],所需要的代价为sum, 然后对于一个分割点t,t > j && t < i,考虑将j + 1 ~ t的点从b[i]改为b[j]的代价,显然对于大于b[i]的点,将其改为b[j]的代价为原来的代价加上b[i] – b[j],而对于原来的小于b[j]的点,它的代价应该减少b[i] – b[j],那么设在j + 1 ~ t之间,大于b[i]的点有x个,小于b[j]的点有y个,那么新的代价就应该是
sum + x * (b[i] – b[j]) – y * (b[i] – b[j])
= sum - (y – x) * (b[i] – b[j])
所以我们要做的就是在i ~ j之间找到是的y – x最大的t,考虑对于固定的点i,如何每一次O(1)的求得可行的转移点j的最优分割点,我们从i – 1开始往左枚举,一直枚举到最小的满足f[j] + 1 == f[i]并且b[j] <= b[i]的点,定义前缀和数组c, c[k]表示,在j + 1 ~ k当中,小于b[i]的点的个数和减去大于b[i]的点的个数和的值,那么我们要求取j的最优的分割点,就是找出满足c[t] – c[k]最大的t, 那么我们从i往左扫到最小的转移点j的过程中记录一下扫过的位置的c[x]最大值即可,至此转移完成。
一些细节:
1、关于如何找到最左边的转移点
首先我们明确一点,若满足j < i 并且,f[i] == f[j],那么b[i] < b[j],否则f[i] >= f[j] + 1,所以我们可以针对每一种f取得的值建立一个vector即pos[x]当中存储所有的f[i] == x的下标,那么我们需要找的是第一个值小于等于当前枚举到的i的点,直接二分即可。
2、关于边界
因为我们这样的转移,是通过找到f值相差1的两个点,来调整中间,但是有一种情况就是调整开头的某一段,或者最后的某一段会被遗漏,所以,我们将原有的b数组,存储到下标2 ~ n + 1,然后在b[1]放入一个-INF, b[n + 2]放入一个INF这样就可以保证肯定能够完成更新最后一段的操作,降低了代码复杂度了。
Source:
/*
created by scarlyw
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <cctype>
#include <ctime>
inline char read() {
static const int IN_LEN = 1024 * 1024;
static char buf[IN_LEN], *s, *t;
if (s == t) {
t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
if (s == t) return -1;
}
return *s++;
}
/*
template<class T>
inline void R(T &x) {
static char c;
static bool iosig;
for (iosig = false, c = read(); !isdigit(c); c = read()) {
if (c == -1) return ;
if (c == '-') iosig = true;
}
for (x = 0; isdigit(c); c = read())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
///*
template<class T>
inline void R(T &x) {
static char c;
static bool iosig;
for (iosig = false, c = getchar(); !isdigit(c); c = getchar())
if (c == '-') iosig = true;
for (x = 0; isdigit(c); c = getchar())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
*oh++ = c;
}
template<class T>
inline void W(T x) {
static int buf[30], cnt;
if (x == 0) write_char('0');
else {
if (x < 0) write_char('-'), x = -x;
for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
while (cnt) write_char(buf[cnt--]);
}
}
inline void flush() {
fwrite(obuf, 1, oh - obuf, stdout);
}
const int MAXN = 35000 + 10;
const int INF = 1000000 + 10;
int n;
int a[MAXN], f[MAXN];
long long dp[MAXN];
std::vector<int> pos[MAXN];
inline void read_in() {
R(n), ++n;
for (int i = 2; i <= n; ++i) R(a[i]), a[i] -= i;
a[1] = -INF, a[++n] = INF - n;
/*处理边界*/
}
int cur[MAXN];
inline int get_pos(int x) {
int l = 0, r = n;
while (l + 1 < r) {
int mid = l + r >> 1;
(cur[mid] > x) ? (r = mid) : (l = mid);
}
return r;
}
inline int get_move(int x, int cur) {
/*返回对应vector中第一个小于等于当前x的数组下标*/
int l = 0, r = pos[cur].size() + 1;
while (l + 1 < r) {
int mid = l + r >> 1;
(a[pos[cur][mid - 1]] > x) ? l = mid : r = mid;
}
return (l == pos[cur].size()) ? (INF) : pos[cur][l];
}
inline void solve() {
/*二分求取lis和f数组*/
for (int i = 1; i <= n; ++i) cur[i] = INF;
for (int i = 1; i <= n; ++i) {
int cur_pos = get_pos(a[i]);
pos[cur_pos].push_back(i), f[i] = cur_pos, cur[cur_pos] = a[i];
}
for (int i = 1; i <= n; ++i) {
int j = get_move(a[i], f[i] - 1);
if (j > i) continue ;
static int c[MAXN];
c[j] = 0, dp[i] = ~0u >> 1;
/*处理小于a[i]的数个数和大于a[i]的数个数差值(前缀和)*/
for (int k = j + 1; k <= i - 1; ++k)
c[k] = c[k - 1] + ((a[k] > a[i]) ? -1 : 1);
int max = c[i - 1]; /*max: 当前走道的最大的前缀和*/
long long sum = 0;
for (int k = i - 1; k >= j; --k) {
max = std::max(max, c[k]);
if (a[k] <= a[i] && f[k] + 1 == f[i]) {
dp[i] = std::min(dp[i], dp[k] + sum - (long long)(max - c[k])
* (long long)(a[i] - a[k]));
}
sum += (long long)abs(a[i] - a[k]);
/*默认全部调整为b[i]*/
}
}
std::cout << n - f[n] << '\n' << dp[n] << '\n';
}
int main() {
read_in();
solve();
return 0;
}