【01背包】百度之星--度度熊剪纸条
标签(空格分隔): 01背包 动态规划
题目:
度度熊有一张纸条和一把剪刀。
纸条上依次写着 N 个数字,数字只可能是 0 或者 1。
度度熊想在纸上剪 K 刀(每一刀只能剪在数字和数字之间),这样就形成了 K+1 段。
他再把这 K+1 段按一定的顺序重新拼起来。
不同的剪和接的方案,可能会得到不同的结果。
度度熊好奇的是,前缀 1 的数量最多能是多少。
Input
有多组数据,读到EOF结束。
对于每一组数据,第一行读入两个数 N 和 K 。
第二行有一个长度为 N 的字符串,依次表示初始时纸条上的N个数。0≤K<N≤10000所有数据N的总和不超过100000
Output
对于每一组数据,输出一个数,表示可能的最大前缀 1 的数量。
Sample Input
5 1
11010
5 2
11010
Sample Output
2
3
思路:
我们自己剪一剪纸条子,很容易可以发现,提取中间的1需要操两回刀,而提取前缀和后缀的1只需要操一回刀。例如,101101,前缀不需要操刀,对中间的11需要在01和10间操刀,然后拼接到前面,尾部的1直接把01剪开就行,这样的话一共需要3刀才能得到最大值。
因此,我们设定第一层循坏为断点位置,第二层循环为操刀数,然后跑一遍01背包。
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e6+5,INF=0x3f3f3f3f;
int n,m,f[maxn],a[maxn],v[maxn],c[maxn],len;
inline int read(){
int s=0,t=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
return s*t;
}
void solve(){
memset(f,0,sizeof(f));
for(int i=1;i<=len;i++){//断点位置
for(int j=m;j>=c[i];j--){//操刀数
f[j]=max(f[j],f[j-c[i]]+v[i]);
//cout<<f[j]<<endl;
}
}
cout<<f[m]<<endl;
}
int main(){
freopen("a.in","r",stdin);
while(scanf("%d%d",&n,&m) == 2 ){
char str[maxn];
memset(a,0,sizeof(a));
memset(v,0,sizeof(v));
memset(c,0,sizeof(c));
cin>>str+1;
for(int i=1;i<=n;i++)a[i]=str[i]-'0';
int k=1;
while(str[k]=='0'){
k++;
}//找到第一个1的位置
int len0=0,len1=0;
len=0;
bool isone=0;
for(int i=k;i<=n;i++){
if(a[i]==0&&isone==1){
isone=0,len0++;//循环到0,把前面的1累加存进数组里
if(len0==1&&a[1]==1){
c[++len]=0;//前缀是1串当然不用操刀
}else{
c[++len]=2;
}
v[len]=len1;
len1=0;
}else if(a[i]==1){
isone=1;len1++;
}
}
if(a[n]==1)c[++len]=1,v[len]=len1;
if(m==0){
if(a[1]==1)cout<<v[1]<<endl;
else cout<<"0"<<endl;
continue;
}
if(a[1]==0)m++;//说明要先从后面操一刀转移到前面
solve();
}
}
之后,奇迹般的WA了。于是我找来了AC代码,拍了一下,7 1 1101110
这个数据的结果不对,原因是我可以在01间操刀,把1110排到110前,即是把中间连续的1排在新串后面,这是我没想过的,怎么处理呢?这时我们可以试着把总操刀数+1
原因:分四种情况,在前缀操刀,在后缀操刀,在中间操刀且只操一次,在中间的普通操刀。
1.在前缀操刀,我们需要把c[1]改为1,这样才能与m+1相对应,其实就相当于把原来f[0]存的数放在了f[1]里而已
2.在后缀操刀,把后缀连续的1拼接到最前面,我们可以把后缀的操刀代价改为2,不进行特判,最后+1的时候可以抵消
3.在中间操刀,因为2、3只能出现一种情况,所以同2
4.普通操刀,要想普通操刀,就必定有前三种情况中的一种,所以这里操刀数加不加1对普通操刀模有影响
AC代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e6+5,INF=0x3f3f3f3f;
int n,m,f[maxn],a[maxn],v[maxn],c[maxn],len;
inline int read(){
int s=0,t=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
return s*t;
}
void solve(){
memset(f,0,sizeof(f));
for(int i=1;i<=len;i++){
for(int j=m;j>=c[i];j--){
f[j]=max(f[j],f[j-c[i]]+v[i]);
//cout<<f[j]<<endl;
}
}
cout<<f[m]<<endl;
}
int main(){
freopen("a.in","r",stdin);
while(scanf("%d%d",&n,&m) == 2 ){
char str[maxn];
memset(a,0,sizeof(a));
memset(v,0,sizeof(v));
memset(c,0,sizeof(c));
cin>>str+1;
for(int i=1;i<=n;i++)a[i]=str[i]-'0';
int k=1;
while(str[k]=='0'){
k++;
}
int len0=0,len1=0;
len=0;
bool isone=0;
for(int i=k;i<=n;i++){
if(a[i]==0&&isone==1){
isone=0,len0++;
if(len0==1&&a[1]==1){
c[++len]=1;
}else{
c[++len]=2;
}
v[len]=len1;
len1=0;
}else if(a[i]==1){
isone=1;len1++;
}
}
if(a[n]==1)c[++len]=1,v[len]=len1;
if(m==0){
if(a[1]==1)cout<<v[1]<<endl;
else cout<<"0"<<endl;
continue;
}
m++;
solve();
}
}
//怎么Markdown上传本地文件还需要会员的,日了