• 【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.在前缀操刀,我们需要把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上传本地文件还需要会员的,日了

  • 相关阅读:
    C# 提取方法重构
    防抖和节流
    利用Object.keys快速循环对象
    MVVM深入理解---小白速会
    异步组件使用详解
    动态组件使用详解
    Vue.$nextTick详解
    深入理解vue .sync修饰符
    vue计算属性---快速上手
    grid-layout 网格布局--快速上手
  • 原文地址:https://www.cnblogs.com/614685877--aakennes/p/12710824.html
Copyright © 2020-2023  润新知