CF229D 动态规划dp

因为我菜所以只能想到消耗空间大,时间复杂度差的做法但是我就是要发!!

首先这种题需要知道他是个dp(看不出来就GG),然后思考下怎么做,首先他是会进行区间合并,并且可以进行连续的合并,也就是把这个过程看做是一个连续区间合并的过程
例:
123456
[1,2] [3,4,5] [6]
可以将一个序列分解为若干个区间(区间长度可以为1),但是区间必须是连续的,不能说你1和3去合并在一起,这是不允许的。
知道他是这样一个过程后要干嘛呢,我们可以设计dp转态
设 f[i][j] 表示[1,i]的序列都合法后,第i个与前j个位置一起合并的最少次数,也就是区间[i-j+1, i]作为一个连续区间。
为什么是这样子记录呢,因为在dp中一个很常见的做法的第二维度记录的是第i个位置所代表的权值。但是在这道题里的问题是权值的取值范围是取决于输入数据,即使离散化后这个权值的取值范围也可能达到一个很大的量级,这会导致我们的空间炸掉。
所以就有了上述的状态转移方程,同样是记录那些状态,但是我们换了种表示方法。

接下来思考转移
我是区间[i-j+1, i]作为一个连续区间,那么我这个区间的权值是固定的,上一个区间的最后一个位置是i-j,那么我们在i-j所在区间是可以获取信息,i-j这个位置往前合并了1个,2个,3个。。。。。这个大小取决于什么时候拿的权值超过当前i的区间所代表的权值就不行了。然后观察每个位置的高都大于1,区间权值具有单调性。我们就对于i-j这个位置最多可以合并多少个位置是可以二分出来的。然后在先前记录一个答案的前缀min,就可以二分后O1的到答案。
复杂度就是O(n^2logn)

int N,f[max_][max_],sum[max_],value[max_][max_];
il int getsum(int L, int R) {
	return sum[R] - sum[L - 1];
}
il void update(int x) {
	value[x][0] = inf;
	for (int i = 1; i <= x; i++) {
		value[x][i] = min(value[x][i - 1], f[x][i]);
	}
}

il int getv(int weizhi, int ht) {
	if (weizhi == 0)return 0; 
	if (getsum(weizhi, weizhi) > ht)return inf;
	int L = 1, R = weizhi;
	while (L < R){
		int mid = ((L + R) >> 1) + 1;
		//[1,mid]//[weizhi,weizhi - mid + 1]
		if (getsum(weizhi - mid + 1,weizhi) <= ht)L = mid;
		else R = mid - 1;
	}
	//[1,R]
	return value[weizhi][R];
}
signed main() {
	N = read();
	for (int i = 1; i <= N; i++) {
		sum[i] = read();
		sum[i] += sum[i - 1];
	}
	memset(f, 127, sizeof(f)); memset(value, 127, sizeof(value));
	f[1][1] = 0; update(1);
	for (int i = 2; i <= N; i++) {
		for (int j = 1; j < i; j++) {
			//[i-j+1,i]
			f[i][j] = getv(i - j, getsum(i - j + 1, i)) + j - 1;
		}
		f[i][i] = i - 1;
		update(i);
	}
	int ans = inf;
	for (int i = 1; i <= N; i++) {
		ans = min(ans, f[N][i]);
	}
	printf("%d", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值