2021年第十二届蓝桥杯A组省赛

时隔9个月,终于想起补题了

编程题

A. 卡片(5分)

答案:3181
在这里插入图片描述

分析:
简单题,直接从1开始处理每一个数,直到出现某一种卡片不够为止。

#include<bits/stdc++.h>
using namespace std;
int main(){
	int cnt[10],ans=1,flag=1;
	for(int i=0;i<=9;i++) cnt[i]=2021;
	while(flag){
		int x=ans;
		while(x){
			int y=x%10; cnt[y]--;
			if(cnt[y]<0) flag=0;
			x/=10;
		}
		if(!flag) break;
		ans++;
	}
	cout<<ans-1<<endl;
	return 0;
} 

B. 直线(5分)

答案:40257
在这里插入图片描述

分析:
通过两点式我们知道(x-x1)/(x2-x1)=(y-y1)/(y2-y1)。将x1= =x2和y1==y2的情况分离出来单独讨论,显然这种情况的结果是20+21,其它的通过化简可知最后的直线方程为
(y2-y1)* x+(x1-x2)* y+(x2* y1-x1* y2)
。只要我们求出了三个参数A=y2-y1,B=x1-x2以及C=x2* y1-x1* y2,我们便可以化简后放在一个容器中vector中,然后再将容器vector放在集合set中,最后的结果就是集合的大小+20+21。

#include<bits/stdc++.h>
using namespace std;
inline int gcd(int a,int b){
	 if(b==0) return a;
	 return a%b?gcd(b,a%b):b;
}
struct Point{
	int x,y;
};
set<vector<int> >s;
vector<Point>e;
int main(){
	int ans,m=20,n=21;
	for(int i=0;i<m;i++)
	   for(int j=0;j<n;j++) e.push_back({i,j});
	for(int i=0;i<e.size();i++){
		for(int j=i+1;j<e.size();j++){
			Point p1=e[i],p2=e[j];
			int x1=e[i].x,y1=e[i].y,x2=e[j].x,y2=e[j].y;
			if(x1==x2||y1==y2) continue;
			int a=y2-y1,b=x1-x2,c=x2*y1-x1*y2;
			int temp=gcd(a,gcd(b,c));
			vector<int>f; f.push_back(a/temp);
			f.push_back(b/temp); f.push_back(c/temp); 
			s.insert(f);
		}
	}
	
	ans=s.size()+m+n;
	cout<<ans<<endl;
	return 0;
} 

C.货物摆放(10分)

答案:2430
在这里插入图片描述

分析:
一个10的16次方的数,它的因子数最多不超过两百,所以我们可以用O(sqrt(n))的时间复杂度求出它的所有的因子(个数假设为x),然后直接三重循环O(x^3) 遍历每一种情况,要是符合则cnt++

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
	ll n=2021041820210418,cnt=0; 
	vector<ll>g; 
	for(ll i=1;i*i<=n;i++){
		if(n%i==0) {
		    g.push_back(i); g.push_back(n/i);
		}
	}
	
	for(int i=0;i<g.size();i++){
		for(int j=0;j<g.size();j++){
			for(int k=0;k<g.size();k++)
			if(g[i]*g[j]*g[k]==n) cnt++;
		}
	}
	cout<<cnt<<endl;
	return 0;
} 

D.路径(10分)

答案:10266837
在这里插入图片描述

分析:
很显然是一道Dijkstra算法的模板题。由于节点个数n=2021,所以最普通的时间复杂度为O(n^2)的Dijkstra算法就可以得出结果;当然我习惯使用优先队列优化后的时间复杂度为O(mlog n)的Dijkstra算法。以下题解为优化后的算法。而对于两个数a和b的最小公倍数来说,我们可以通过求出最大公因数gcd(a,b),然后最小公倍数lcm(a,b)便是a*b/gcd(a,b)

#include<bits/stdc++.h>
using namespace std;
#define INF 0x7fffffff
const int maxn=1e4+10;
typedef long long ll;
struct Edge{
	int v,w;
	bool operator < (const Edge&b)const{
		return w>b.w;
	}
};
ll d[2050],vis[2050];
vector<Edge>g[maxn];
inline gcd(int a,int b){
	return a%b?gcd(b,a%b):b;
}
inline lcm(int a,int b){
	return a*b/gcd(a,b);
}
void Dijkstar(){
	priority_queue<Edge>q;
	q.push({1,0}); d[1]=0; 
	while(!q.empty()){
		Edge x=q.top();q.pop();
		int u=x.v;
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=0;i<g[u].size();i++){
			int v=g[u][i].v,w=g[u][i].w;
			if(d[u]+w<d[v]) {
				d[v]=d[u]+w;
				q.push({v,d[v]});
			}
		}
	}
	cout<<d[2021]<<endl;
}
int main(){
    int n=2021;
    for(int i=1;i<=2021;i++) d[i]=INF,vis[i]=0;
    for(int i=1;i<=n;i++){
    	for(int j=i+1;j<=n;j++){
    		if(j-i<=21)  {g[i].push_back({j,lcm(j,i)});g[j].push_back({i,lcm(j,i)});}
		}
	}
	Dijkstar();
	return 0;
} 

E. 回路计数(15分)

答案:881012367360

在这里插入图片描述

分析:
一道很难的状压DP
f[i][j]用来表示以结点j结尾得到状态i的方案数。状态i的二进制表示即目前已经过的结点。

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define endl '\n'
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define PII pair<int,int>
#define pb(x) push_back(x)
typedef long long LL;
const int maxn=1e5+100;
const int N=1e5+100;
const int mod=1e9+7; 
int n=21,m=(1<<n)-1;
LL ans=0;
LL f[(1<<22)][24],a[30][30];
int gcd(int a,int b){
	return b?gcd(b,a%b):a;
} 
int main(){
	memset(a,0,sizeof(a));
    for(int i=1;i<=n;i++){
    	for(int j=1;j<=n;j++){
    		if(gcd(i,j)==1) a[i][j]=1;
		}
	}
	f[1][1]=1;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			if(i&(1<<(j-1))){
				for(int k=1;k<=n;k++){
					if(a[j][k]&&((1<<k-1)&((i-(1<<(j-1)))))) f[i][j]+=f[i-(1<<(j-1))][k];
				}
			}
		}
	}
	for(int i=1;i<=n;i++) ans+=f[m][i];
	cout<<ans<<endl;
	return 0;
}

F. 砝码称重(15分)

在这里插入图片描述
在这里插入图片描述

分析:
第一道编程题,肯定不会太难,直接暴力
由于砝码的总重量w为1~100000,我们可以开一个标记数组visit[100010],记录是否某一个重量 i 可以达到。由于砝码的个数n为1~100,我们可以遍历每一个砝码,并且开通辅助动态数组vector< int > g,g用来存储现在可以称出的重量 。对每一个砝码进行以下三种处理:
1. 只称当前砝码重量,若未标记则加入g中。
2. 将当前砝码重量与g中每一个可能出现的砝码放在不同的两边,即重量相减,并取绝对值,若未标记则加入g中。
3. 将当前砝码重量与g中每一个可能出现的砝码放在同一边,即重量相加,若未标记则加入g中。

时间复杂度为:O(n*w)

#include <iostream>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
#define INF 0x7fffffff
const int maxn = 1e5 + 10;
int a[105];
bool visit[maxn];
vector<int>g;
int main(){   
    int n; cin >> n; 
    for (int i = 1; i <= maxn-10; i++) visit[i] = false;
    for (int i = 1; i <= n; i++) cin >> a[i];
    sort(a + 1, a + 1 + n);
    g.push_back(a[1]); visit[a[1]] = true;
    for (int i = 2; i <= n; i++) {
    	int len=g.size();
        for (int j = 0; j < len; j++) {
            int temp1 = a[i], temp2 = abs(a[i] - g[j]), temp3 = a[i] + g[j];
            if (temp1 > 0 && !visit[temp1]) visit[temp1] = true, g.push_back(temp1);
            if (temp2 > 0 && !visit[temp2]) visit[temp2] = true, g.push_back(temp2);
            if (temp3 > 0 && !visit[temp3]) visit[temp3] = true, g.push_back(temp3);
        }
    }
    cout << g.size();
    return 0;
}


G. 异或数列(20分)
在这里插入图片描述

分析:
一道位运算的题。首先,假设Alice的最后数字为A,Bob是B,如果是平局的话,那么A= =B,即A^B==0 。所以当所有的输入进行异或结果是0的话,则平局,否则不是平局。 我们用一个数字cnt[maxn]来记录二进制从高位到低位的每一个数字出现次数;不难发现,若最高位只出现一次,结果一定是Alice赢,因为Alice先手。 其他情况我们分开讨论:
1. 若最高位出现奇数次,且n为奇数:则一定是先手赢
2. 若最高位出现奇数次,且n为偶数,则一定是后手赢
3. 若最高位出现偶数次,则分析次高位,以此类推。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
int cnt[maxn];
int main()
{
    int t; cin>>t;
    while(t--){
    	memset(cnt,0,sizeof(cnt));
    	int n,ans=0; cin>>n;
    	for(int i=1;i<=n;i++){
    		int x; cin>>x;
    		for(int i=0;i<20;i++){
    			if(x&(1<<i)) cnt[i]++;
			}
			ans=ans^x;
		}
		bool flag=false;
		if(ans==0) cout<<0<<endl;
        else{
        	for(int i=19;i>=0;i--){
        		if(cnt[i]==1) {
        			ans=1;
        			break;
				}
        		else if(cnt[i]&1){
        			if(n&1) ans=1;
        			else ans=-1;
        			break;
				}
				else continue;
 			}
			 cout<<ans<<endl;
 		}	
	}
    return 0;
}

H.左孩子右兄弟(20分)
在这里插入图片描述

在这里插入图片描述

分析:
一道简单深搜(dfs)题。对于每一棵子树的父节点u而言,他的最大长度就是子树v的个数加上它所有子树中所能达到的最大长度;所以我们只需要对每一个子节点dfs一次,记录它的最大长度,最后结果相加即可。
时间复杂度:O(n)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
vector<int>g[maxn];
int ans=0;
int dfs(int u){
	int res=0;
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		res=max(res,dfs(v));
	}
	res+=g[u].size();
	return res;
}
int main()
{
    int n;cin>>n;
    for(int i=2;i<=n;i++){
    	int x; cin>>x;
    	g[x].push_back(i);
	}
	cout<<dfs(1)<<endl;
    return 0;
}


I.括号序列(25分)
在这里插入图片描述

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

&が&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值