题面大意是有一个长度为n的01串,每一段连续长度为x的1串的花费为a_1=1,d=1的等差数列和S_x。问把最少多少个1变成0,可以让总花费小于m。
第一眼看到题目很容易想到一个错误的想法:每次取最大的1段二分,这样每一刀肯定是性价比最高的。每次贪心的取性价比最高的分法。(这样肯定是最优的嘛
然后再想一下,有可能会有一些性价比没这么高的分法,但是恰好花费比m小,而性价比高的分法的花费可能会比m小很多。同时使前者的改变次数比后者少。例如:
5 3
11111
前者用三等分的做法处理后为:10101,ans=2;
而已知后者ans=3;
- 单的贪心性价比分法不能得到最优解,那么我们可不可以尝试一下贪心一下改变次数呢?
- 每次枚举就计算一下当前次数的最少花费是否小于m。
- 简单计算一下时间复杂度,枚举次数(n)*搜索(n*n)=(n^3)
- 用记忆化搜索剪枝一下就是
O(能过)
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 550;
const int inf = 1e9;
int n,m;
char s[maxn];
int dp[maxn][maxn];
inline int val(int x){
return x*(x+1)/2;
}
inline int cal(int p,int k){
if(k<0)return inf;
if(p==n)return 0;
int &res=dp[p][k],i;
if(~res)return res;
if(s[p]=='0')return cal(p+1,k);
res=inf;
for(i=p;i<n&&s[i]=='1';++i){
res=min(res,cal(i+1,k-1)+val(i-p));
if(!res)return 0;
}
res=min(res,cal(i,k)+val(i-p));
return res;
}
int main(){
memset(dp,-1,sizeof dp);
scanf("%d%d%s",&n,&m,s);
for(int i=0;i<=n;++i){
if(cal(0,i)<=m){
printf("%d\n",i);
break;
}
}
}