第十二届蓝桥杯大赛软件赛省赛

试题A:卡片


题解

#include<bits/stdc++.h>
using namespace std;
int a[10];
bool check(int x){
	while(x){
		int t=x%10;
		if(!a[t])return false;
		--a[t];
		x/=10;
	}
	return true;
}
int main(){
	for(int i=0;i<10;++i)a[i]=2021;
	int ans=0;
	while(1){
		++ans;
		if(!check(ans))break;
	}
	cout<<ans-1;
	return 0;
}

答案:3181

试题B:直线


题解

法一

手动设置精度,小于该精度即认为相等

#include<bits/stdc++.h>
using namespace std;
#define db double
struct no{
	int x,y;
	no(int a,int b):x(a),y(b){}
};
db eps=1e-6;
vector<no>point;
vector<pair<db,db>>ans;
int main(){
	for(int i=0;i<20;++i)
	for(int j=0;j<21;++j){
		point.push_back(no(i,j));
	}
	int tot=point.size();
	for(int i=0;i<tot;++i)
	for(int j=i+1;j<tot;++j){
		db x1=point[i].x,y1=point[i].y;
		db x2=point[j].x,y2=point[j].y;
		if(x1==x2||y1==y2)continue;
		db k=(y1-y2)/(x1-x2);
		db b=y1-k*x1;
		int flag=1;
		for(int c=0;c<ans.size();++c){
			if(fabs(k-ans[c].first)<eps&&fabs(b-ans[c].second)<eps){
				flag=0;break;
			}
		}
		if(flag)ans.push_back(make_pair(k,b));
	}
	cout<<ans.size()+20+21;
	return 0;
}

法二

修改b的表达式:\(b=\frac{y_2*x_1-y_1*x_2}{x_1-x_2}\),就像\(\frac{2}{3} = \frac{4}{6}\)防止误差产生。

#include<bits/stdc++.h>
using namespace std;
#define db double
struct no{
	int x,y;
	no(int a,int b):x(a),y(b){}
};
db eps=1e-6;
vector<no>point;
set<pair<db,db>>ans;
int main(){
	for(int i=0;i<20;++i)
	for(int j=0;j<21;++j){
		point.push_back(no(i,j));
	}
	int tot=point.size();
	for(int i=0;i<tot;++i)
	for(int j=i+1;j<tot;++j){
		db x1=point[i].x,y1=point[i].y;
		db x2=point[j].x,y2=point[j].y;
		if(x1==x2||y1==y2)continue;
		db k=(y1-y2)/(x1-x2);
		db b=(y2*x1-y1*x2)/(x1-x2);
		int flag=1;
		ans.insert({k,b});
	}
	cout<<ans.size()+20+21;
	return 0;
}

答案:40257

试题C:货物摆放


题解
数字超级大,但质因数只有8个,因数只有128个,暴力枚举即可
顺便说一下因数个数等于不同质因数的指数分别加1后相乘的积

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const ll N=2021041820210418;
ll prime[100],divs[1000];
int myexp[100];
int cntp,cntd;
void get_p(){
	ll  x=N;
	for(int i=2;i*i<=x;++i){
		if(x%i==0){
			prime[cntp]=i;
			while(x%i==0)myexp[cntp]++,x/=i;
			++cntp;
		}
	}
	if(x>1)prime[cntp]=x,myexp[cntp++]=1;
}
void dfs(int now,ll tot){
	if(now>=cntp){
		divs[cntd++]=tot;
		return ;
	}
	ll base=1;
	for(int i=0;i<=myexp[now];++i){
		dfs(now+1,tot*base);
		base*=prime[now];
	}
}
int ans=0;
int main(){
	get_p();
	printf("质因数有%d个:\n",cntp);
	for(int i=0;i<cntp;++i)printf("%d ",prime[i]);
	putchar('\n');
	dfs(0,1);
	printf("因数有%d个:\n",cntd);
	for(int i=0;i<cntd;++i)printf("%lld ",divs[i]);
	putchar('\n');
	for(int i=0;i<cntd;++i)
	for(int j=0;j<cntd;++j)
	for(int k=0;k<cntd;++k)
	if(divs[i]*divs[j]*divs[k]==N)++ans;
	printf("答案:%d",ans);
	return 0;
}

答案:

试题D:路径


题解
求最短路常用的三种方法:
  1.弗洛伊德(Floyd-Warshall algorithm)多源最短路
  2.spfa(贝尔曼福特(Bellman-Ford)的队列优化)单源最短
  3.迪杰斯特拉(Dijkstra)单源最短
这里贴一个dijkstra

#include<bits/stdc++.h>
using namespace std;
const int N=2050;
struct no{
	int x,id;
	no(int a,int b):id(a),x(b){}
	no(){}
	const bool operator<(const no&a)const{
		return x>a.x;//与小根堆配套使用
	}
};
int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}
int g[N][N],dis[N],book[N];
priority_queue<no>q;
void dij(){
	dis[1]=0;
	q.push(no(1,0));
	while(q.size()){
		no now=q.top();
		q.pop();
		if(book[now.id])continue;
		book[now.id]=1;
		for(int i=1;i<=2021;++i){
			if(dis[i]>dis[now.id]+g[now.id][i]){
				dis[i]=dis[now.id]+g[now.id][i];
				q.push(no(i,dis[i]));
			}
		}
	}
}
int main(){
	memset(dis,0x3f,sizeof(dis));
	memset(g,0x3f,sizeof(g));
	for(int i=1;i<=2021;++i)
	for(int j=i+1;j<=i+21;++j){
		g[i][j]=i*j/gcd(i,j);
		g[j][i]=g[i][j];
	}
	dij();
	cout<<dis[2021];
	return 0;
}

答案:10266837

试题E:回路计数


题解

状压 dp 是动态规划的一种,通过将状态压缩为整数来达到优化转移的目的。 ---OI Wiki
状态压缩的思想是用二进制来表示状态。用一个整数的二进制形式的每一个0或1表示一个状态。

f[i][j]表示当前处在点i状态为j

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=21;
const int M=1<<N;
int g[N][N];
int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}
ll f[N][M];
int main(){
	for(int i=0;i<N;++i)
	for(int j=i+1;j<N;++j){
		if(gcd(i+1,j+1)==1)g[i][j]=g[j][i]=1;
	}
	f[0][1]=1;
	for(int i=2;i<M;++i)//从小到大遍历每一种状态
	for(int j=0;j<N;++j){
		if((i&(1<<j))==0)continue;
		for(int k=0;k<N;++k){
			if((i&(1<<k))==0||j==k)continue;
			if(g[j][k]) f[j][i]+=f[k][i-(1<<j)];
		}
	}
	ll ans=0;
	for(int i=1;i<N;++i)ans+=f[i][M-1];
	cout<<ans;
	return 0;
}

注意==优先级高于&

答案:881012367360

试题F:砝码称重


题解
动态规划
f[i][j]表示前i个砝码是否能称重j
注意状态转移方程有三个
若前i-1个砝码能表示质量x
则增加一个砝码后
1.\(x+a[i]\)
2.\(x-a[i](x>=a[i])\)
3.\(a[i]-x(a[i]>=x)\)
能表示以上三种质量

#include<bits/stdc++.h>
using namespace std;
const int N=105;
const int M=1e5+5;
bool f[N][M];
int a[N],tot;
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)cin>>a[i],tot+=a[i];
    f[0][0]=1;//初始化
    for(int i=1;i<=n;++i)
    for(int j=0;j<=tot;++j){
        f[i][j]|=f[i-1][j];//继承一下前面的值
        if(j>=a[i])f[i][j]|=f[i-1][j-a[i]];
        if(j+a[i]<=tot)f[i][j]|=f[i-1][j+a[i]];
        if(a[i]>=j)f[i][j]|=f[i-1][a[i]-j];
    }
    int ans=0;
    for(int i=1;i<=tot;++i)ans+=f[n][i];
    cout<<ans;
    return 0;
}

试题G:异或数列


对于所有评测用例,\(1\le T\le 2e5\),\(1 \le \sum^T_{i=1}n_i \le 2e5\),\(0 \le X_i \le 2^{20}\).
题解
对于此类问题的两个思考方向
1.记忆化搜索
2.找规律
显然记忆化搜索空间不够而且太复杂,所以我们找规律
注意到无论哪种分配方式,两名玩家的数列异或起来的值为定值,若该值为0,则必定平局。(两个数字异或等于0,则两个数字相等)
现在考虑所有数字的二进制,用count记录所有数字异或结果的位数,用flag记录所有数字中,位数为count的个数,size为所有数字的个数。(位数为count的数简称count数)
首先可以得到flag必为奇数且count数对答案有关键影响,因为位数比count高的位在异或之后会抵消。
1.\(flag==1\),则先手必赢,只要他拿到这个数就获得了异或结果的最高位。
2.\(size\%2==1\),先手赢,先手只要先拿一个count数就可以采用策略是自己手中的count数的个数位奇。
3.\(size\%2==0\),后手赢,因为后手可以采用策略是自己手中的count数的个数为奇。

#include<bits/stdc++.h>
using namespace std;
int main(){
  int T;
  cin>>T;
  while(T--){
    vector<int>a;
    int n;
    cin>>n;
    for(int i=0;i<n;++i){
      int temp;cin>>temp;a.push_back(temp);
    }
    int sum=0;
    for(auto i:a)sum^=i;
    if(sum==0){
      cout<<0<<endl;continue;
    }
    int count=0;
    while(sum){
      sum>>=1;++count;
    }
    int flag=0;
    for(auto i:a){
      if((i>>(count-1))&1)++flag;
    }
    if(flag==1){cout<<1<<endl;continue;}
    if(a.size()%2)cout<<1<<endl;
    else cout<<-1<<endl;
  }
  return 0;
}

试题H:左孩子右兄弟




题解
树形DP
用f[i]表示以i为根节点。转化为二叉树的高度。则有:
f[i]=i的孩子节点个数+max(f[j]),j为i的孩子节点
从样例中可以很直观的看出i的孩子节点个数为f[i]的一部分,只要将最大的f[j]放到最下面,就可以得到上式。

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

试题I:括号序列


y总的视频
题解
合法括号序列需要满足的两条性质:
1.左括号总数等于右括号总数
2.任意前缀序列中左括号数大于等于右括号数
令cnt=左括号数-右括号数,从左向右遍历整个序列并维护cnt。
当cnt<0时,不满足性质2,则当前位置之前必须添加若干左括号使\(cnt\ge0\),遍历完之后若cnt>0则在添加若干右括号。
我们先考虑添加左括号,f[i][j]表示第i个右括号前添加j个左括号的方案数
add[i]表示第i个右括号前至少应添加的左括号数
f[i][j]=f[i-1][0]+f[i-1][1]\(\cdots\)f[i-1][j]
那么就得到了\(n^3\)的算法

for(int i=1;i<=num;++i)//num为右括号的个数
for(int j=add[i];j<=len;++j)//len为括号总数
for(int k=0;k<=j;++k){
    f[i][j]+=f[i-1][k];
}

但过不了5k的数据
f[i][j]=f[i-1][0]+f[i-1][1]\(\cdots\)f[i-1][j]
f[i][j+1]=f[i-1][0]+f[i-1][1]\(\cdots\)f[i-1][j+1]
则有f[i][j+1]=f[i][j]+f[i-1][j+1]
此时算法复杂度降低到了\(n^2\)
添加右括号时,我们将字符串反转,左括号变为右括号,右括号变为左括号,再调用相同的算法即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const int N=5e3+5;
const int mod=1e9+7;
char s[N];
int len;
int add[N];
int f[N][N];
ll work(){
    int num=0,cntl=0,cntr=0;
    memset(add,0,sizeof(add));
    memset(f,0,sizeof(f));
    for(int i=1;i<=len;++i){
        if(s[i]=='('){
            ++cntl;
        }
        else {
            ++num;
            ++cntr;
            if(cntl)--cntl,--cntr;
            add[num]=cntr;
        }
    }
    if(!num)return 1;//这里注意一下
    for(int i=add[1];i<=len;++i)f[1][i]=1;//初始化
    for(int i=1;i<=num;++i){
        for(int j=0;j<=add[i];++j)
        f[i][add[i]]=(f[i][add[i]]+f[i-1][j])%mod;//第一个值单独计算
        for(int j=add[i]+1;j<=len;++j)
        f[i][j]=(f[i][j-1]+f[i-1][j])%mod;
    }
    return f[num][add[num]];
}
int main(){
    scanf("%s",s+1);
    len=strlen(s+1);
    ll l=work();
    reverse(s+1,s+1+len);
    for(int i=1;i<=len;++i)
    if(s[i]=='(')s[i]=')';
    else s[i]='(';
    ll r=work();
    cout<<(l*r)%mod;
    return 0;
}

试题J:分果果

不会

posted @ 2022-01-28 13:53  何太狼  阅读(82)  评论(0编辑  收藏  举报