蓝桥杯23年第十四届省赛真题-填充|DFS,贪心

时间:2024-03-29 17:46:20

题目链接:

1.填充 - 蓝桥云课 (lanqiao.cn)

蓝桥杯2023年第十四届省赛真题-填充 - C语言网 (dotcpp.com)

说明: 

dfs就不再多说了,对于每个?都有0和1两个分支,数据范围是:

那么有m个 ?,时间复杂度就是 O(2^{m}),会超时。蓝桥杯官网可以过35%的数据,暴力先拿到一部分再说。

这里需要注意的是:对于?的字符的每层,最后要把它复原,恢复为?

DFS代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
string s;
int mx=0;
int len;

//参数含义:当前搜索层数k,搜索到第k个字符(从0开始),cn:前面是否有一个单个的字符可以出现00或11串  
//num当前找到的不重叠的00或11子串 数量 
void dfs(int k,int cn,int num){
	
	//剩下所有字符都可以组成合法子串加上当前数量都比已经找到的数量小,不再搜索 
	if(num+(len-k)/2<mx) return;
	
	//k个字符都已经搜索过,递归出口 
	if(k==len){
		if(num>mx){
			mx=num;
		}
		
		return;
	}
	
	if(s[k]!='?'){
		
		//同时满足 前面是否有一个单个的字符,两个字符相同才计数 ,单个字符数计为0 
		if(cn==1&&s[k]==s[k-1]){
	 		dfs(k+1,0,num+1);
		 }
		else{
		//不满足,不计数,该字符是一个单一的字符 
			dfs(k+1,1,num);
		}
	}
	
	else{
		s[k]='0';
		if(cn==1&&s[k]==s[k-1])
		dfs(k+1,0,num+1);
		else
		dfs(k+1,1,num);
		
		
		s[k]='1';
		if(cn==1&&s[k]==s[k-1])
		dfs(k+1,0,num+1);
		else
		dfs(k+1,1,num);
		
	    //退出前 要还原成'?' 
	    s[k]='?'; 
		
	 }
} 

signed main() {
 
   ios::sync_with_stdio(0);
   cin.tie(0);
   cout.tie(0);
    
   cin>>s;
   len=s.size();
   int cnt=0;
   
   
   for(int i=0;i<len;i++){
   	
   	if(s[i]!='?'){
   		
   		//i为0时特别处理,防止i-1越界 
   		if(i==0){
   			cnt=1;
		   }
		else{
		   	if(cnt==1&&s[i]==s[i-1]){
		   		cnt=0;
		   		mx++;
			   }
			else{
			   cnt=1;	
		    }
		   }
   		
	   }
	   
	int n=mx;
   	if(s[i]=='?'){
   		
   		//i为0时特别处理,防止i-1越界 
   	if(i==0) 
	{
		s[i]='0';
		dfs(i+1,1,n);
		s[i]='1';
		dfs(i+1,1,n);
	}
   		
   	else{
   		dfs(i,cnt,n);
	   }	
   		
		   
		break;
	}
	   
	   
	   
   }

  cout<<mx;
  return 0;
}


/*
测试数据
01?1001???101?1?0000?0110????0?001?110?0?00?01?1??

答案:22 

*/




贪心:当前能组成一个合法子串的时候我就组合起来,因为如果我放弃这个组合,我最多让后面一个字符和它后面的字符组成一个合法子串,而浪费了我当前这个字符,组合更多的合法子串就是尽量利用每一个可以利用的字符。
再观察可得: 
 '00、11、0?、1?、?1、?0、??'都是正确的,01,10 不行 。可以组成合法子串时,这两个字符已经成对不用再看了,跳过这两个字符,不能成对,看下个字符,因为下个字符可能和他的下个字符成对,我们需要 利用每一个可以利用的字符成对 。

贪心代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;

int len;
int mx=0;//最大值 

signed main() {
 
   ios::sync_with_stdio(0);
   cin.tie(0);
   cout.tie(0);
   string s;
   
   cin>>s;
   len=s.size();
   
   for(int i=0;i<len-1;){
   	//贪心,当前能组成一个合法子串的时候我就组合起来,因为如果我放弃这个组合
	//我最多让后面一个字符和它后面的字符组成一个合法子串,而浪费了我当前这个字符
	//组合更多的合法子串就是尽量利用每一个可以利用的字符
	//再观察可得: 
	//'00、11、0?、1?、?1、?0、??'都是正确的,01,10 不行 
	//可以组成合法子串时,这两个字符已经成对不用再看了,跳过这两个字符,不能成对,看下个字符,因为下个字符可能和他的下个字符成对
	//需要 利用每一个可以利用的字符成对 
	
	if(s[i]==s[i+1]||s[i]=='?'||s[i+1]=='?') {
		i+=2;
		mx++;
	}
   	else   i++;
   }

  cout<<mx;
  return 0;
}

 参考:蓝桥杯真题讲解:填充(贪心)-CSDN博客

的另一个版本 ,更好想更好梳理思路一些。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;

int len;
int mx=0;//最大值 
bool st[N];//标记是否被匹配过 
signed main() {
 
   ios::sync_with_stdio(0);
   cin.tie(0);
   cout.tie(0);
   string s;
   
   cin>>s;
   len=s.size();
   
//   for(int i=0;i<len-1;){
   	//贪心,当前能组成一个合法子串的时候我就组合起来,因为如果我放弃这个组合
	//我最多让后面一个字符和它后面的字符组成一个合法子串,而浪费了我当前这个字符
	//组合更多的合法子串就是尽量利用每一个可以利用的字符
	//再观察可得: 
	//'00、11、0?、1?、?1、?0、??'都是正确的,01,10 不行 
	//可以组成合法子串时,这两个字符已经成对不用再看了,跳过这两个字符,不能成对,看下个字符,因为下个字符可能和他的下个字符成对
	//需要 利用每一个可以利用的字符成对 
	
//	if(s[i]==s[i+1]||s[i]=='?'||s[i+1]=='?') {
//		i+=2;
//		mx++;
//	}
//   	else   i++;
//   }
  
  //另一种角度的贪心,每个字符优先跟前一个字符匹配,不能,再跟后面的字符匹配
   for(int i=0;i<len;i++){
   	
   	//因为是一个一个挪的 首先要判断 当前 字符是否已经匹配过 
   	if(s[i]!='?'&&!st[i]){
   	if(i-1>=0&&(s[i-1]==s[i])&&!st[i-1])
   	{
   		st[i]=true;
   		st[i-1]=true;
   		mx++;
	}
   	else if(i+1<len&&(s[i+1]==s[i]))
   	{
   		st[i]=true;
   		st[i+1]=true;
   		mx++;
	}
			
	}
   	
   	
   	//?可以匹配 任意落单的字符   
	else{
		if(st[i]) continue;
		
		if(i-1>=0&&!st[i-1])
   	{
   		s[i]=s[i-1]; 
   		st[i]=true;
   		st[i-1]=true;
   		mx++;
	}
   	else if(i+1<len)
   	{
   		s[i]=s[i+1]; 
   		st[i]=true;
   		st[i+1]=true;
   		mx++;
	}
		
		
	}
	
	
   }
   

  cout<<mx;
  return 0;
}


/*
测试数据
01?1001???101?1?0000?0110????0?001?110?0?00?01?1??

答案:22 

*/