第十二届蓝桥杯省赛题解(最后两题没写完)

第一次蓝桥杯,没有考好,太久没有做oi赛制了,感觉自己太过于谨慎,以至于做题速度太慢。写篇题解纪念一下。

题解如下

    • A:空间
      • 题目描述:
      • 问题分析:
    • B:卡片
      • 题目描述:
      • 问题分析:
    • C:直线
      • 问题描述:
      • 问题分析:
    • D:货物摆放
      • 题目描述:
      • 问题分析:
    • E:路径
      • 题目描述:
      • 问题分析:
    • F:时间显示
      • 问题描述:
      • 问题分析:
    • G:砝码称重
      • 题目描述:
      • 问题分析:
    • H:杨辉三角形
      • 问题描述:
      • 问题分析
    • I:双向排列

A:空间

题目描述:

256MB的内存空间开一个数组,数组的每个元素都是32位二进制整数,如果不考虑程序占用的空间和维护内存需要的辅助空间,请问256MB的空间可以存储多少个32位二进制整数。

问题分析:

  • 一字节(1B)有8比特,一个32位整数有32比特,256 * 1024 * 1024 / 4即可

  • 答案:67108864

B:卡片

题目描述:

有2021个0-9数字,从1开始拼整数,用完就不能再拼其他数字了,求最多可以拼到数字几?(从1开始连续拼整数)

问题分析:

  • 从1开始,循环计算每一个数字的每位数出现的次数,直到有个数字使用次数超过2021,则当前数字不能被拼出来,答案即为上一个数字

  • 答案:3181

附上代码

#include
using namespace std;

int cnt[10];
int main(){
	for(int i=1;i;i++){
		int x=i;
		while(x){
			cnt[x%10]++;
			if(cnt[x%10]>2021){
				cout<<i-1;
				return 0;
			}
			x=x/10;
		}
	}
	return 0;
}

C:直线

问题描述:

给定平面上20x21个整点(x,y)|0<=x<20,0<=y<21,求这些点确定了多少条不同的直线?

问题分析:

  • 枚举所有点对,构成一条直线,在表中查找直线,判断该直线是否曾经出现过即可,没有出现过则ans++,并将直线存入表中。那么如果唯一的表示一条线段呢?

  • y=kx+b?这里的k如果是double型的可能会出现精度误差,导致计算出错。(我就是这么错的,所以我的答案是48953,下次不敢了)

  • 其实只要能够唯一的表示一条直线就可以了,我们可以采用最简分数来存储 k 和 b 。即 k 和 b 都有分子fz,和分母fm。为了确保唯一性,当k<0时,我们让fz为正,fm为负。

  • 考虑到斜率不存在的情况,由于斜率不存在的直线肯定只有20条,我们直接不考虑斜率不存在的情况,即x2=x1,最后再加上20条即可。由于斜率=0的已知,顺便也不去计算斜率为0,直接最后加上21条斜率为0的直线即可。

  • 答案:40257

附上代码:(需要跑个12s,可以set优化,自己尝试吧)

#include
using namespace std;

struct node{
	int fz,fm;
}k[1000000],b[1000000];

long long kfz,kfm,bfz,bfm,ans;

int gcd(int a,int b){
	if(b==0)return a;
	else return gcd(b,a%b);
}

void check(){
	if(kfz<0){
		kfz=-kfz;
		kfm=-kfm;
	}
	if(bfz<0){
		bfz=-bfz;
		bfm=-bfm;
	}
	for(int i=1;i<=ans;i++){
		if(kfz==k[i].fz&&kfm==k[i].fm&&bfz==b[i].fz&&bfm==b[i].fm)return;
	}
	ans++;
	k[ans].fz=kfz;
	k[ans].fm=kfm;
	b[ans].fz=bfz;
	b[ans].fm=bfm;
	
	//cout<
}

int main(){
	long long t1,t2;
	
	for(int x1=0;x1<20;x1++){
		for(int y1=0;y1<21;y1++){
			for(int x2=0;x2<20;x2++){
				for(int y2=0;y2<21;y2++){
					if(x1==x2)continue;
					if(y2==y1)continue;
					
					//处理k 
					t1=y2-y1;
					t2=x2-x1;
					kfz=t1/gcd(t1,t2);
					kfm=t2/gcd(t1,t2);
					
					//处理b 
					t1=(y1*kfm-kfz*x1);
					t2=kfm;
					bfz=t1/gcd(t1,t2);
					bfm=t2/gcd(t1,t2);
					
					check();
				} 
			}
		} 
	}
	cout<<ans+20+21;
	return 0;
}

D:货物摆放

题目描述:

给定n=2021041820210418,求有多少组L,W,H满足 n= L * W * H 。
ps:1 1 4 和 1 4 1为不同的两组方案。

问题分析:

  • 显然L,W,H是n的因子。尝试对n进行质因子分解,发现其只有8个质因子,2,3,3,3,17,131,2857,5882353。
  • 用这些因子组合L,W,H,看看有多少种方法即可,可以手动排列组合算,但是我懒,我选择用dfs枚举三个因子(由质因子相乘得到),最后检验 LWH = n 并查表看看{L,W,H}是否出现过即可。由于得到的因子过大,三个因子相乘爆long long。考虑等价成质因子:2,3,3,3,5,7,11,13。这样就可以放心dfs了
  • 答案:2430

代码如下

#include
using namespace std;

struct node{
	int x,y,z;
}p[10000];
int a[10]={0,2,3,3,3,5,7,11,13};
int goal=2*3*3*3*5*7*11*13,ans;

void dfs(int dep,long long x,long long y,long long z){
	if(dep==9){
		if(x*y*z!=goal)return;
		for(int i=1;i<=ans;i++){
			if(x==p[i].x&&y==p[i].y&&z==p[i].z)return;
		}
		ans++;
		p[ans].x=x;
		p[ans].y=y;
		p[ans].z=z;
		return;
	}
	dfs(dep+1,x*a[dep],y*a[dep],z*a[dep]);
	dfs(dep+1,x*a[dep],y*a[dep],z);
	dfs(dep+1,x*a[dep],y,z*a[dep]);
	dfs(dep+1,x*a[dep],y,z);
	dfs(dep+1,x,y*a[dep],z*a[dep]);
	dfs(dep+1,x,y*a[dep],z);
	dfs(dep+1,x,y,z*a[dep]);
	dfs(dep+1,x,y,z);
}
int main(){
    dfs(1,1,1,1);
    cout<<ans;
	return 0;
}

E:路径

题目描述:

一张图有2021个结点,依次编号1-2021。如果a,b相差绝对值小于等于21,则a和b之间有一条长度为lcm(a,b)的无向边。求1到2021的最短路。

问题分析:

  • 知道 lcm= i x j /gcd(i,j),且知道spfa,dij即可,是一道裸的最短路模板题

  • 答案:10266837

代码如下(前向星存图,spfa板子)

#include
using namespace std;

struct edge{
	int u,v,w,next;
}e[1000100];
int vex[100010],k,vis[100010],dis[100010];
queue<int>q;

void add(int u,int v,int w){
	k++;
	e[k].u=u;
	e[k].v=v;
	e[k].w=w;
	e[k].next=vex[u];
	vex[u]=k;
}

void spfa(){
	for(int i=2;i<=2021;i++)dis[i]=1e9;
	q.push(1);
	while(!q.empty()){
		int u=q.front();
		vis[u]=0;
		for(int i=vex[u];i;i=e[i].next){
			int v=e[i].v;
		    if(dis[v]>dis[u]+e[i].w){
		    	dis[v]=dis[u]+e[i].w;
		    	if(vis[v]==0){
		    		vis[v]=1;
		    		q.push(v);
				}
			}
		}
		q.pop();
	}
	cout<<dis[2021];
}

int gcd(int a,int b){
	if(b==0)return a;
	else return gcd(b,a%b);
}
int main(){
    for(int i=1;i<=2021;i++){
    	for(int j=i+1;j<=i+21&&j<=2021;j++){
    		add(i,j,i*j/gcd(i,j));
    		add(j,i,i*j/gcd(i,j));
		}
	}
	spfa();
	return 0;
}

F:时间显示

问题描述:

求0:00:00经过n毫秒后是几时几分几秒?不足两位要补前导0。n<=1e18

样例:
n=46800999 ,ans=13:00:00
n=1618708103123,ans=01:08:23

问题分析:

注意毫秒除以1000,再把天86400s余掉,求出经过的小时,分钟秒就可以了。

代码如下

#include
using namespace std;
int main(){
    long long n;
    cin>>n;
    n=n/1000;
    n=n%86400;
    int x=n/3600;
    int y=(n-3600*x)/60;
    int z=n-3600*x-y*60;
    printf("%02d:%02d:%02d",x,y,z);
	return 0;
}

G:砝码称重

题目描述:

求定一个天平和N个砝码,砝码重量分别为W1,W2,……,W N _{N} N
计算其一共可以称出多少种不同的重量?
N<=100, ∑ \sum Wi<=100000
注:砝码可以放在任意一边

样例:N=3,W=1,4,6,ans=10
1,6-4,4-1,4,4+1,6,6+1,6+4-1,6+4,6+4+1

问题分析:

  • 看上去是道背包变形,看到数据范围100 x 100000果然是。
  • 设f[i][j]表示有前 i 个砝码,能否称出质量 j 。1表示能,0表示不能。
  • 状态转移:f[i][j]=f[i-1][j-a[i] | f[i-1][j+a[i] | f[i-1][j]
  • 由于使用n个砝码时可能前面的砝码是放在另一边的,相当于f[i][j] = 1,j为负值。为了避免负下标,我们统一加一个base(base=100000)

代码如下

#include
using namespace std;

int f[101][210010],a[101],base=1e5;
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    
    f[0][0+base]=1;
    for(int i=1;i<=n;i++){
    	for(int j=base+base;j>=0;j--){
    		if(f[i-1][j])f[i][j]=1;
    		if(j+a[i]<=2*base&&f[i-1][j+a[i]])f[i][j]=1;
    		if(j-a[i]>=0&&f[i-1][j-a[i]])f[i][j]=1;
		}
	}
	int ans=0;
	for(int i=1+base;i<=2*base;i++){
		if(f[n][i])ans++;
	}
	cout<<ans;
	return 0;
}

H:杨辉三角形

问题描述:

给定N,求N最早出现在杨辉三角的第几项
N<=1e9

样例:
N=6,ans=13
N=10,ans=18

问题分析

  • 一个很好想到的思路,就是直接把杨辉三角的表打出来。直到出现数字N。
  • 但是,例如N=999999,其必然不会出现在杨辉三角的中间部分,而只会出现在第N+1层的第二个元素。如果数字N第一次出现在第N+1层的第二个元素,那非常好算,就是N(N+1)/2+2即可。
  • 一个小思路:在打表的过程中,如果Nn),答案只能出现在C(N,1)。
  • 打表的过程中,一旦元素大于N,直接break即可。这样时间复杂度基本ok了。

代码如下

#include
using namespace std;

long long C[50000][500];

int main(){
    long long n;
    cin>>n;
    if(n==1){
    	cout<<1;
    	return 0;
	}
	if(n==2){
		cout<<5;
		return 0;
	}
    C[0][0]=1;
    C[1][0]=1;
    C[1][1]=1;
    for(long long i=2;i<50000;i++){
    	C[i][0]=1;
    	C[i][1]=i;
    	C[i][2]=i*(i-1)/2;
    	if(n<C[i][2])break;
    	if(n==C[i][1]){
    		cout<<i*(i+1)/2+2;
    		return 0;
		}
    	if(n==C[i][2]){
    		cout<<i*(i+1)/2+3;
    		return 0;
		} 
    	for(long long j=3;j<=i;j++){
    		C[i][j]=C[i-1][j-1]+C[i-1][j];
    		if(C[i][j]>n)break;
    		if(C[i][j]==n){
    			cout<<i*(i+1)/2+j+1;
    			return 0;
			}
		}
	}
	cout<<n*(n+1)/2+2;
	return 0;
}

I:双向排列

第十二届蓝桥杯省赛题解(最后两题没写完)_第1张图片
第十二届蓝桥杯省赛题解(最后两题没写完)_第2张图片

第十二届蓝桥杯省赛题解(最后两题没写完)_第3张图片

你可能感兴趣的:(各比赛题解)