bzoj1049 数字序列

数字序列

题目背景:

bzoj1049

分析: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, 然后对于一个分割点tt > 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之间找到是的 x最大的t,考虑对于固定的点i,如何每一次O(1)的求得可行的转移点j的最优分割点,我们从 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取得的值建立一个vectorpos[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;
}


 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值