【01背包】百度之星--度度熊剪纸条

【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.在前缀操刀,我们可以把前缀1的操刀代价改为1,最后+1的时候可以相互抵消
2.在后缀操刀,我们可以把后缀的操刀代价改为2,不进行特判,最后+1的时候也可以抵消
3.在中间操刀,同理
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上传本地文件还需要会员的,日了

猜你喜欢

转载自www.cnblogs.com/614685877--aakennes/p/12710824.html