蓝桥杯任意刷

受伤的皇后

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N=15;
int a[N][N];
int n,m;
int res=0;
int d[N];
bool ok(int x,int y){
    for(int i=1;i<x;i++){
        if(d[i]==y)return false;
        if(abs(x-i)==abs(y-d[i])){
            if(x-i<3)return false;
        }
    }
    return true;
    
}
void dfs(int u){//当前需要拜访第u行 
    if(u==n+1){
        res++; 
        return ;
    }
    for(int i=1;i<=n;i++){
        if(ok(u,i)){
            d[u]=i;
            dfs(u+1);
        }
    }
}
int main(){
scanf("%d",&n);
dfs(1);
printf("%d",res);
    return 0;
}

全球变暖(最大联通子集,一趟递归记得不要嵌套计数)

#include <iostream>
using namespace std;
const int N=1005;
char a[N][N];
bool vis[N][N];
int n,m;
int dx[5]={0,0,1,-1};
int dy[5]={1,-1,0,0};
int res=0;
int ans=0;
bool inside(int x,int y){
	return x>=1&&x<=n&&y>=1&&y<=n;
}
bool flag;
void dfs(int u,int v){
	if(!flag){
		int cnt=0;
		for(int i=0;i<4;i++){
			int cx=u+dx[i];
			int cy=v+dy[i];
			if(inside(cx,cy)&&a[cx][cy]=='#')cnt++;	
		} 
		if(cnt==4){
			ans++;
	//只是这样则 一个岛屿中存在x个点不会被淹没就会错判成残余x个岛,
//	其实只有一个 
			flag=true;
		}

	}
	
	for(int i=0;i<4;i++){
		int cx=u+dx[i];
		int cy=v+dy[i];
//		if(inside(cx,cy)&&a[cx][cy]=='#')cnt++;
		if(inside(cx,cy)&&!vis[cx][cy]&&a[cx][cy]=='#'){
			vis[cx][cy]=true;
			dfs(cx,cy);
		}
		
	} 
	
}
int main(){
	
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j]=='#'&&!vis[i][j]){
				vis[i][j]=true;//本可以不用vis,直接赋值 '.',这里复习一下 
				res++;//最大联通子集的个数 
				flag=false;
				dfs(i,j);
			}
		}
	}
	printf("%d",res-ans);
	return 0;
} 

游园问题

在这里插入图片描述
输入输出样例
输入

WoAiLanQiaoBei

输出

AiLanQiao

这是普通的动态规划,时间复杂度 O ( n 2 ) O(n^2) O(n2),能过70%

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
vector<string> str;
int dp[N];
int main(){
    string s,ss;
    cin>>s;
    int cnt=0;
    for(int i=0;i<s.size();i++){
        if(s[i]>='A'&&s[i]<='Z'){
            if(i)str.push_back(ss);
            ss="";
            ss+=s[i];
        }
        else ss+=s[i];
    }
    str.push_back(ss);
    int tail=0;//最长子序列最后一个元素在原数组str中的位置
    int len=0;
    for(int i=0;i<str.size();i++){
        dp[i]=1;
        for(int j=0;j<i;j++){
            if(str[i]>str[j]){
//                if(dp[j]+1>dp[i]){
//                    dp[i]=dp[j]+1;
//                }
                dp[i]=max(dp[j]+1,dp[i]);
            }
        }
        if(dp[i]>=len){//不加等号正确率直降10%
//        如果只是求最长子序列的长度,无需加等号,但要求最小字典序的最长子序列
//        为什么此时 长度为len,末尾元素str[i]一定小于 先前遇见的
//		长度为len 末尾元素为str[j]
//        反证法,如果str[j](长度为len)<str[i],那么此时长度一定大于len而不会等于
            len=dp[i];
            tail=i;
            //记录最长子序列的长度 和 最长子序列最后一个元素在原数组中的位置
        }
    }
//    知道原数组中每一个元素 作为子序列末尾元素对应的子序列长度
//    知道最长子序列的长度和末尾元素在原数组中的位置tail,就可以 从tail开始
//	反向遍历原数组,进而找 对应着长度为len-1子序列的最后一个元
    string res="";
    for(int i=tail;i>=0;i--){
        if(dp[i]==len){
            len--;
//            res+=str[i];//这样的话还得 reverse(res.begin(),res.end());
            res=str[i]+res;
        }
    }
   cout<<res;
    return 0;
}

题目意思,给定一串游客字符串,给定的顺序是预约顺序
需要从中选取尽可能多的游客(按游客字典序上升),按预约顺序输出选取的游客

如何保证队列中最后存放的就是最长上升子序列
首先假设队列Q中有一个元素 str【1】

遍历str数组, str【i】> Q.back(),则 str【i】插入队列Q(步骤一)

str【i】< Q.back(),则str【i】可能会小于Q.back()以及
其之前的(按照这种插入Q的方式,Q中元素一定是升序排列的)
那么str【i】应该放在队列中哪个位置->Q中大于str【i】的第一个元素的位置(步骤二)
(反证法可以证明Q中元素是升序的,可以进行二分)
在这里插入图片描述

但还没搞懂这贪心怎么证,贪心的道理倒是很容易清楚
队列Q是一路贪心的结果,具有最优子结构(maybe)
首先铁律是上升子序列要尽可能长(步骤一)

相同长度的上升子序列,要尽量保证每一个元素都尽可能小
(在不打乱上升性质的前提下)(动态规划实现的细节就是
相同长度的上升子序列的最大元素应该是尽可能小)(步骤二)
这里最后的队列Q ,可以做到长度为k且最大元素最小的子序列 就是Q(1~k)

(“我们这么理解,同样长度的子序列,是不是末尾的人越矮,
后面可以接着排队的人越多呢?所以这个就是贪心的精髓。”)

如果没理解错的话,拥有了队列Q可以,随时获取 给定长度的最小字典值的上升子序列
而正因为拥有这种性质(长度k-1的子序列字典值越小,更可能获得更长的子序列)


错了,Q队列存放的就是 Q[i]对应的是 长度为i的子序列的最小末尾元素
Q队列根本不是一个完整的最长子序列

好吧,转了一圈还是不知道为什么 遍历str去让更新Q就可以得到Q

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
vector<string> str;
vector<string> Q;
int main(){
    string s,ss;
    cin>>s;
    int cnt=0;
    for(int i=0;i<s.size();i++){
        if(s[i]>='A'&&s[i]<='Z'){
            if(i)str.push_back(ss);
            ss="";
            ss+=s[i];
        }
        else ss+=s[i];
    }
    str.push_back(ss);

    Q.push_back(str[0]);
     
    vector<int> len;
    len.push_back(1); //Q[0]是长度为1的子序列对应的最后一个元素 
    
    for(int i=1;i<str.size();i++){
        if(str[i]>Q.back()){
            Q.push_back(str[i]);
            len.push_back(Q.size());
        }
        else{
        
			
		    int l=0,r=Q.size()-1;
            while(l<r){
                int mid=(l+r)/2;
                if(Q[mid]>=str[i]){//替换掉Q中第一个大于等于str[i]的元素
//				二分的判断条件要根据题意来决定,不能每次都替换绝对大于她的 可能
//				比如  str[i]是6 ,Q 有一段是 5 6 7 8,不能替换7而应该是6
                    r=mid;
                }
                else l=mid+1;
            }
            Q[l]=str[i];
            len.push_back(l+1);
        } 
    }
    //	题目要求按预约顺序输出,于是最后按字典序排列的Q用不上
//	相当于需要输出路径,
//	最后得到的有最长子序列的长度sz,
//	可以记录 
//	str中每个元素作为子序列最后一个元素的子序列长度,其实也就是str中
//	被选中最后放在Q中的元素 在Q中的位置,可能会有多个str中的元素曾
//	试图摆放在Q的同一个位置,但是根据步骤二,一定是str中更后面的元素
//	最终成功留在Q中 
//	遍历看str中哪个元素对应的是 长度为sz的子序列的最后一个元素 
//	应该从后往前遍历str(更大的长度对应的最后一个元素一定在后面)
//	
//	记录str中被选中元素在Q中的位置 或者 记录 Q中选中元素在str中的位置 
    int sz=Q.size();
    int p=str.size()-1;
    vector<string> res;

    while(sz){
        if(len[p]==sz){
            sz--;//接下来找Q[sz-1]对应的str中的元素 
            res.push_back(str[p]);  
        }
        p--;
    } 
    for(int i=Q.size()-1;i>=0;i--){
        cout<<res[i];
    }
    return 0;
}
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
vector<string> str;
vector<string> Q;
int main(){
    string s,ss;
    cin>>s;
    int cnt=0;
    for(int i=0;i<s.size();i++){
        if(s[i]>='A'&&s[i]<='Z'){
            if(i)str.push_back(ss);
            ss="";
            ss+=s[i];
        }
        else ss+=s[i];
    }
    str.push_back(ss);

    Q.push_back(str[0]);
     
    vector<int> pos;
    pos.push_back(0); //Q[0]是长度为1的子序列对应的最后一个元素 
    
    for(int i=1;i<str.size();i++){
        if(str[i]>Q.back()){
            Q.push_back(str[i]);
            pos.push_back(Q.size()-1);
        }
        else{
        
			
		    int l=0,r=Q.size()-1;
            while(l<r){
                int mid=(l+r)/2;
                if(Q[mid]>=str[i]){//替换掉Q中第一个大于等于str[i]的元素
//				二分的判断条件要根据题意来决定,不能每次都替换绝对大于她的 可能
//				比如  str[i]是6 ,Q 有一段是 5 6 7 8,不能替换7而应该是6
                    r=mid;
                }
                else l=mid+1;
            }
            Q[l]=str[i];
            pos.push_back(l);
        } 
    }


//	记录str中被选中元素在Q中的位置 或者 记录 Q中选中元素在str中的位置 
    int sz=Q.size();
    int p=str.size()-1;
    vector<string> res;

    while(sz){
        if(pos[p]+1==sz){//len[p]代表str[p]在Q中的位置 从0存储 子序列最末尾元素的位置+1=子序列长度
            sz--;//接下来找Q[sz-1]对应的str中的元素 
            res.push_back(str[p]);  
        }
        p--;
    } 
    for(int i=Q.size()-1;i>=0;i--){
        cout<<res[i];
    }
    return 0;
}

错误输出路径

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
vector<string> str;
vector<string> Q;
int main(){
    string s,ss;
    cin>>s;
    int cnt=0;
    for(int i=0;i<s.size();i++){
        if(s[i]>='A'&&s[i]<='Z'){
            if(i)str.push_back(ss);
            ss="";
            ss+=s[i];
        }
        else ss+=s[i];
    }
    str.push_back(ss);

    Q.push_back(str[0]);
     
    vector<int> pos;
	pos.push_back(0);
//    pos.push_back(0); //Q[0]是长度为1的子序列对应的最后一个元素 
//    string pos[N];
    for(int i=1;i<str.size();i++){
        if(str[i]>Q.back()){
            Q.push_back(str[i]);
//            pos[Q.size()-1]=i;
				pos.push_back(i);
        }
        else{	
		    int l=0,r=Q.size()-1;
            while(l<r){
                int mid=(l+r)/2;
                if(Q[mid]>=str[i]){//替换掉Q中第一个大于等于str[i]的元素
//				二分的判断条件要根据题意来决定,不能每次都替换绝对大于她的 可能
//				比如  str[i]是6 ,Q 有一段是 5 6 7 8,不能替换7而应该是6
                    r=mid;
                }
                else l=mid+1;
            }
            Q[l]=str[i];
            pos[l]=i;
        } 
    }


//	记录str中被选中元素在Q中的位置 或者 记录 Q中选中元素在str中的位置 
	for(int i=0;i<Q.size();i++){
//		cout<<str[pos[i]];
cout<<Q[i];
	}
   
    return 0;
}

另一种输出路径,只过了20%,耐心耗尽

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
vector<string> str;
vector<string> Q;
int main(){
    string s,ss;
    cin>>s;
    int cnt=0;
    for(int i=0;i<s.size();i++){
        if(s[i]>='A'&&s[i]<='Z'){
            if(i)str.push_back(ss);
            ss="";
            ss+=s[i];
        }
        else ss+=s[i];
    }
    str.push_back(ss);

    Q.push_back(str[0]);
     
//    vector<int> pos;
//    pos.push_back(0); 
    int pos[N];
    pos[0]=0;//Q[0]对应的是str[0]中的0位置
    int pre[N];
    memset(pre,-1,sizeof(pre));
    for(int i=1;i<str.size();i++){
        if(str[i]>Q.back()){
            Q.push_back(str[i]);
            
            pos[Q.size()-1]=i;
            pre[i]=pos[Q.size()-2];
        }
        else{
		    int l=0,r=Q.size()-1;
            while(l<r){
                int mid=(l+r)/2;
                if(Q[mid]>=str[i]){//替换掉Q中第一个大于等于str[i]的元素
//				二分的判断条件要根据题意来决定,不能每次都替换绝对大于她的 可能
//				比如  str[i]是6 ,Q 有一段是 5 6 7 8,不能替换7而应该是6
                    r=mid;
                }
                else l=mid+1;
            }
            Q[l]=str[i];
            pos[l]=i;
            pre[i]=pos[l-1];
        } 
    }

  	int f=pos[Q.size()-1];
    stack<int> sta;
    while(pre[f] != -1){
        sta.push(f);
        f = pre[f];
    }
    sta.push(f);
    while(!sta.empty()){
        cout<<str[sta.top()];
        sta.pop();
    }
    return 0;
}

正解这种输出路径的方法

#include <bits/stdc++.h>
using namespace std;

char members[999995][12];
char * b[999995];
int p[999995];
int path[999995];

bool cmp(char * x, char * y){
    while ( (*x>='a' && *x<='z' || *x>='A' && *x<='Z') && (*y>='a' && *y<='z' || *y>='A' && *y<='Z') ){
        if (*x < *y) return true;
        if (*x > *y) return false;
        x++;
        y++;
    }
    if (*y != 0) return true;
    return false;
}

int find(int ll, int rr, char * s){
    int mid = (ll+rr)/2;
    while (ll<rr){
        if ( cmp(b[mid], s) ){
            ll=mid+1;
        } else {
            rr=mid;
        }
        mid = (ll+rr)/2;
    }
    return ll;
}

int main(){
    memset(path, -1, sizeof(path)-1);
    char x;
    x = getchar();
    int i=-1;
    int j=0;
    while( x >= 'a' && x <= 'z' || x >= 'A' && x <= 'Z' ){
        if ( x >= 'A' && x <= 'Z' ){
            i++;
            members[i][0] = x;
            j=1;
        } else {
            members[i][j] = x;
            j++;
        }
        x = getchar();
    }
    b[0] = members[0];
    p[0] = 0;
    int r=0;
    for (int k=1;k<=i;k++){
        if ( cmp(b[r], members[k]) ){
            r++;
            b[r] = members[k];
            p[r] = k;
            path[k] = p[r-1];
        } else {
            int pos = find(0, r, members[k]);
            if (pos > 0) path[k] = p[pos-1];
            b[pos] = members[k];
            p[pos] = k;
        }
    }
    int f=p[r];
    stack<int> sta;
    while(path[f] != -1){
        sta.push(f);
        f = path[f];
    }
    sta.push(f);
    while(!sta.empty()){
        cout<<members[sta.top()];
        sta.pop();
    }
    return 0;
}

解法二用普通数组k可以AC但在dev上崩了,用vector就可以
参考博文
力扣贪心解释
线段树解法

金额查错

一是要去重,二是小的要先去,先递归进 st【u】=true 的分支

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int a[N];
bool f[N];
	int m,n;
	int sum=0;
void dfs(int u,int s){
		
	if(u==n){
	if(s==sum-m){
			for(int i=0;i<n;i++)if(f[i])cout<<a[i]<<" ";
			cout<<endl;
			return;
		}
		return ;
	}

//	出现了两次 3 4 去重,如果数组中没有重复数组选择是不会重复的
//	可以用map存储每次f数组,数组中有两个3,这是根源,但是别忘了a数组是排序好了的
  		if(u<n&&u > 1) {
		    if(a[u]==a[u-1]&&f[u-1]==false) {
		      u++;
		    }
  		}//这样可以确保 要么都选 要么只选相同中的第一个
  		
  	f[u]=true;//金额按照从小到大排列 一定是 先取小的,小的数要先取
	dfs(u+1,s+a[u]);
	
	f[u]=false;
	dfs(u+1,s);
	
	
}
int main(){

	cin>>m>>n;
	
	for(int i=0;i<n;i++){
		cin>>a[i];
		sum+=a[i];
	}
	sort(a,a+n);
	dfs(0,0);
	
	return 0;
}

如果小的数先不取,结果乱套了,选的数超过了m,即使让s>m时推出递归也无济于事
所以还是把小的先取,先递归进 st【u】=true 的分支

蓝肽子序列(最长公共子序列)

在这里插入图片描述

动态规划考虑到 下标-1的位置 不能越界(下届),用vector数组时可以先push 下标为0处任何一个值

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
vector<string> s1,s2;
int dp[N][N];
int main(){//最长公共子序列
    string str1,str2;
    cin>>str1>>str2;
    string ss="*";
    s1.push_back(ss);
    s2.push_back(ss);
    ss="";
    for(int i=0;i<str1.size();i++){
        if(str1[i]>='A'&&str1[i]<='Z'){
            if(i){
                s1.push_back(ss);
                ss="";
                
            }
            ss+=str1[i];
        }
        else ss+=str1[i];
    }
    s1.push_back(ss);
    ss="";
    for(int i=0;i<str2.size();i++){
        if(str2[i]>='A'&&str2[i]<='Z'){
            if(i){
                s2.push_back(ss);
                ss="";
                
            }
            ss+=str2[i];
        }
        else ss+=str2[i];
    }
    s2.push_back(ss);
    for(int i=1;i<s1.size();i++){
        for(int j=1;j<s2.size();j++){
            if(s1[i]==s2[j]){
                dp[i][j]=dp[i-1][j-1]+1;
                
            }
            else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
        }
    }
    cout<<dp[s1.size()-1][s2.size()-1];
    return 0;
}

123(二分)

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=1500000;
#define ll long long int
ll a[N];//前缀长度
ll sum[N];//前缀和

ll preSum(ll x){
	ll l=1,r=N;//从小往大找,左边界一定要合法,一开始l取成0 1~?区间的求解就会出错,而从右往左这个左边界就,,反正能过
	while(l<r){
		ll mid=(l+r)>>1;从小往大找
		if(a[mid]<x){//找到 a[mid]>=x 且a[mid-1]<x,那么下标x就在第mid行
			l=mid+1;
		}
		else r=mid;
//		ll  mid = l + r + 1 >> 1;//从大往小找
//        if (a[mid] > x) r = mid - 1;//找到 a[mid]<=x 且a[mid+1]>x,那么下标x就在第mid行
//        else l = mid;
	}
//	return sum[l] + a[x - a[l]];
	return sum[l-1]+a[x-a[l-1]];
}
int main(){//最长公共子序列
	int T;
	cin>>T;
//	行数i,(1+i)*i/2<=1e12  i<=sqrt(2)* 1e6 < 150000 序列的长度小于150000
	for(int i=1;i<N;i++){
		a[i]=a[i-1]+i;
		sum[i]=sum[i-1]+a[i];
//		注意前i行的长度 的值 恰好等于 第i行的和
	}
	ll  l,r;
	while(T--){
		scanf("%lld %lld",&l,&r);
		printf("%lld\n",preSum(r)-preSum(l-1));
	}
    return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=1500000;
#define ll long long int
ll a[N];//前缀长度
ll sum[N];//前缀和

ll preSum(ll x){
	ll l=1,r=N;//从小往大找,左边界一定要合法,一开始l取成0 1~?区间的求解就会出错
	while(l<r){
	
		ll  mid = l + r + 1 >> 1;//从大往小找
        if (a[mid] > x) r = mid - 1;//找到 a[mid]<=x 且a[mid+1]>x,那么下标x就在第mid行
        else l = mid;
	}
	return sum[l] + a[x - a[l]];
}
int main(){//最长公共子序列
	int T;
	cin>>T;
//	行数i,(1+i)*i/2<=1e12  i<=sqrt(2)* 1e6 < 150000 序列的长度小于150000
	for(int i=1;i<N;i++){
		a[i]=a[i-1]+i;
		sum[i]=sum[i-1]+a[i];
//		注意前i行的长度 的值 恰好等于 第i行的和
	}
	ll  l,r;
	while(T--){
		scanf("%lld %lld",&l,&r);
		printf("%lld\n",preSum(r)-preSum(l-1));
	}
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值