【WC2019模拟2019.1.12】二分的代价(状态互换dp)

Description:

在这里插入图片描述

题解:

非常容易想到一个 O ( n 3 ) O(n^3) O(n3)的dp,于是做不动了。

考虑一个数变大,这个序列的答案只会更大,所以答案不会超过 9 ∗ l o g   n 9*log~n 9log n

因此状态互换。

f i , j f_{i,j} fi,j表示从i开始,在花费j的代价最远能走到哪里。

枚举一个点p:
f i , j = m a x ( f i , j − 1 , [ f i , j − a [ p ] > = p − 1 ] ∗ f p + 1 , j − a [ p ] ) f_{i,j}=max(f_{i,j-1},[f_{i,j-a[p]}>=p-1]*f_{p+1,j-a[p]}) fi,j=max(fi,j1,[fi,ja[p]>=p1]fp+1,ja[p])

发现瓶颈在a[p],不妨对a[p]相同的一起做,维护前缀max,p在j的前面虽然意义上不合法,但是答案只会更劣,所以不用担心答案会错,这是一个简单的套路。

转移复杂度 O ( n ∗ a n s ) O(n*ans) O(nans),维护前缀max复杂度 O ( n ∗ a n s ∗ 10 ) O(n*ans*10) O(nans10),卡卡常数就能过。

实际上有更优的复杂度:
再设 g i , j g_{i,j} gi,j表示从i开始,代价为j,最左能够到哪儿。

这两个东西可以互相转移,也是用维护前后缀max的方式,比较巧妙。

#include<cstdio>
#include<cstring>
#define pp printf
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int N = 1e5 + 5;

int n, a[N], f[154][N], mx[N];
char s[N];

int main() {
	freopen("cost.in", "r", stdin);
	freopen("cost.out", "w", stdout);
	scanf("%s", s + 1); n = strlen(s + 1);
	fo(i, 1, n) a[i] = s[i] - '0';
	fo(i, 1, n) f[a[i]][i] = i, f[0][i] = i - 1;
	fo(j, 0, 153) f[j][n + 1] = n;
	fo(j, 1, 153) {
		fo(i, 1, n) f[j][i] = max(f[j][i], f[j - 1][i]);
		 fo(k, 1, 9) {
		 	if(j < k) break;
		 	fo(i, 1, n) if(a[i] == k)
			 	mx[i] = max(mx[i - 1], f[j - a[i]][i + 1]); else
			 	mx[i] = mx[i - 1];
		 	fo(i, 1, n) mx[i] = max(mx[i], mx[i - 1]);
			fo(i, 1, n) f[j][i] = max(f[j][i], mx[f[j - k][i] + 1]);
		 }
		 if(f[j][1] >= n) {
			printf("%d\n", j); return 0; 
		}
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值