第九届蓝桥杯大赛软件类国赛

第九届蓝桥杯大赛软件类国赛

这些题官网还没有解答的,我主要参考了b站UP主大雪菜的解法(绝大部分题先自己做了一遍),当然也网上查了一些解答,但发现现在网上的一些解法并不正确,希望可以给大家一个参考。

具体题目官网可以下载。

我的代码应该避免不了错误(填空题还是可以保证的 o_o …),如果有任何问题,欢迎大家评论指正。

国赛C++ A组

三角形面积

已知三角形三个顶点在直角坐标系下的坐标分别为:
(2.3, 2.5)
(6.4, 3.1)
(5.1, 7.2)

求该三角形的面积。

注意,要提交的是一个小数形式表示的浮点数。
要求精确到小数后3位,如不足3位,需要补零。

思路
有很多方法:

海伦公式:(p=(a+b+c)/2) S=sqrt[p(p-a)(p-b)(p-c)]

余弦定理求出一个角,然后面积即为底*高/2

二维向量叉乘公式a(x1,y1),b(x2,y2),则a×b=(x1y2-x2y1),面积即为两边组成的向量叉乘/2

我挑一个初中就学了的公式吧(其实我只记得这个,数学老师原谅我 /_ \ ),结果是8.795

#include<iostream>
#include<cmath>
#include<cstdio>

using namespace std;

inline double getL(pair<double,double> &p1,pair<double,double> &p2){
	double dx = p1.first - p2.first;
	double dy = p1.second - p2.second;
	return sqrt(dx*dx + dy*dy);
}

int main(){
	pair<double,double> p1 = make_pair(2.3, 2.5);
	pair<double,double> p2 = make_pair(6.4, 3.1);
	pair<double,double> p3 = make_pair(5.1, 7.2);
	
	double a = getL(p1,p2),b = getL(p1,p3),c = getL(p2,p3);
	// 方法2 先求a b夹角f 再求0.5*a*b*sin(f) 
	// 夹角用余弦定理 2*a*b*cosf = a*a + b*b - c*c 
	double f = acos((a*a+b*b-c*c)/(2*a*b));
	double res1 = 0.5*a*b*sin(f);
	cout << res1 << endl;
	return 0;
}

阅兵方阵

x国要参加同盟阅兵活动。
主办方要求每个加盟国派出的士兵恰好能组成 2 个方阵。
x国发现弱小的 y国派出了130人的队伍,他们的士兵在行进中可以变换2种队形:
130 = 81 + 49 = 9^2 + 7^2
130 = 121 + 9 = 11^2 + 3^2

x国君很受刺激,觉得x国面积是y国的6倍,理应变出更多队形。
于是他发号施令:
我们要派出一支队伍,在行进中要变出 12 种队形!!!

手下人可惨了,要忙着计算至少多少人才能组成 12 种不同的双方阵。
请你利用计算机的优势来计算一下,至少需要多少士兵。

(ps: 不要失去信心,1105人就能组成4种队形了)

注意,需要提交的是一个整数,表示至少需要士兵数目,不要填写任何多余的内容。

思路
这个题比较简单,答案是160225,我先想到的是这种:

#include<iostream>
#include<map>
using namespace std;

// 根据队形数目调整N
const int N = 1010; 
int a[N];

int main(){
	for(int i=0;i<N;i++) a[i] = i*i;
	
	map<int,int> mp;
	// 枚举时规定左边<=右边 
	for(int i=1;i<N;i++)
		for(int j=i;j<N;j++){
			mp[a[i]+a[j]]++;
			if(mp[a[i]+a[j]] == 12)	{
				cout << a[i]+a[j] << endl; // 160225
				return 0;
			}	
		}
	return 0;
}

也可以用这种方法:

#include<iostream>
#include<cmath>
using namespace std;

int main(){
	for(int x=1;;x++){
		int cnt = 0;
		// 规定左边 i <= 右边 r
		for(int i=1;i*i*2<=x;i++){
			int r = sqrt(x-i*i);
			if(r*r+i*i == x) cnt++;
		}
		if(cnt == 12){
			cout << x << endl;
			return 0;
		}
	}
	return 0;
}

找假币

在8枚硬币中,有1枚假币,假币外观与真币一模一样,只是重量略轻或略重一点。
给你一架天平,要求最多称3次,就找出假币,并且知道它是重一些还是轻一些。
下面的代码给出一个解决方案,仔细分析逻辑,填写划线位置缺少的代码。

#include <stdio.h>

int balance(int a, int b)
{
	if(a<b) return -1;
	if(a>b) return 1;
	return 0;
}

void judge(char* data, int a, int b, int std)
{
	switch(balance(data[a],data[std])){
	case -1:
		printf("%d light\n", a);
		break;
	case 0:
		printf("%d heavy\n", b);
		break;
	case 1:
		printf("err!\n", b);
	}
}

// data 中8个元素,有一个假币,或轻或重
void f(char* data)
{
	switch( ____________________________________ ){  // 填空
	case -1:
		switch(balance(data[0]+data[4],data[3]+data[1])){
			case -1:
				judge(data,0,3,1);
				break;
			case 0:
				judge(data,2,5,0);
				break;
			case 1:
				judge(data,1,4,0);
		}
		break;
	case 0:
		judge(data,6,7,0);		
		break;
	case 1:
		switch(balance(data[0]+data[4],data[3]+data[1])){
			case -1:
				judge(data,4,1,0);
				break;
			case 0:
				judge(data,5,2,0);
				break;
			case 1:
				judge(data,3,0,1);
		}
		break;		
	}	
}

int main()
{
	int n;
	char buf[100];
	
	scanf("%d", &n);
	gets(buf);
	
	int i;
	for(i=0; i<n; i++){
		gets(buf);
		f(buf);
	}
	
	return 0;
}

请注意:只需要填写划线部分缺少的内容,不要抄写已有的代码或符号。

思路

给的代码有点问题

case 0:
		judge(data,6,7,0);		
		break;

需要改为

case 0:
		switch(balance(data[7],data[6])){
			case -1:
				judge(data,7,6,0);
				break;
			case 0:
				printf("err!\n");
				break;
			case 1:
				judge(data,6,7,0);
		}	
		break;

测试数据,可以通过这些数据暴力推出答案:

16
10000000
01000000
00100000
00010000
00001000
00000100
00000010
00000001
12222222
21222222
22122222
22212222
22221222
22222122
22222212
22222221

最终版本:(包含我写的一些推导注释,关键就是看case 0反推,答案为balance(data[0]+data[1]+data[2],data[3]+data[4]+data[5])

#include <stdio.h>

int balance(int a, int b)
{
	if(a<b) return -1;
	if(a>b) return 1;
	return 0;
}

// 隐含条件,a<b 且std为真硬币 
void judge(char* data, int a, int b, int std)
{
	switch(balance(data[a],data[std])){
	case -1:
		printf("%d light\n", a);
		break;
	case 0:
		printf("%d heavy\n", b);
		break;
	case 1:
		printf("err!\n", b);
	}
}

// data 中8个元素,有一个假币,或轻或重
void f(char* data)
{
	switch(balance(data[0]+data[1]+data[2],data[3]+data[4]+data[5])){  // 填空
	case -1:
		switch(balance(data[0]+data[4],data[3]+data[1])){
			case -1:
				judge(data,0,3,1);
				break;
			case 0:
				judge(data,2,5,0);
				break;
			case 1:
				judge(data,1,4,0);
		}
		break;
	// 根据case -1 0 1判断填空为balance(xxx,xxx)
	// 填空判断的两个质量,相等时(case 0:),直接 judge,且认为最后两个(6,7)可能是答案 
	// 那么填空比较的一定是前6个,且各为3个
	case 0:
		switch(balance(data[7],data[6])){
			case -1:
				judge(data,7,6,0);
				break;
			case 0:
				printf("err!\n");
				break;
			case 1:
				judge(data,6,7,0);
		}	
		break;
	// 且右边大时,判断04和31,当04和31相等时,假币为25 
	// 那么填空很可能是比较了01x和34x,或者03x和14x 
	// 这个时候猜肯定先猜012,345呗 ,一测试正确就OK了 
	case 1:
		switch(balance(data[0]+data[4],data[3]+data[1])){
			case -1:
				judge(data,4,1,0);
				break;
			case 0:
				judge(data,5,2,0);
				break;
			case 1:
				judge(data,3,0,1);
		}
		break;		
	}	
}

int main()
{
	int n;
	char buf[100];
	
	scanf("%d", &n);
	gets(buf);
	
	int i;
	for(i=0; i<n; i++){
		gets(buf);
		f(buf);
	}
	
	return 0;
}

约瑟夫环 – important

n 个人的编号是 1~n,如果他们依编号按顺时针排成一个圆圈,从编号是1的人开始顺时针报数。
(报数是从1报起)当报到 k 的时候,这个人就退出游戏圈。下一个人重新从1开始报数。
求最后剩下的人的编号。这就是著名的约瑟夫环问题。

本题目就是已知 n,k 的情况下,求最后剩下的人的编号。

题目的输入是一行,2个空格分开的整数n, k
要求输出一个整数,表示最后剩下的人的编号。

约定:0 < n,k < 1百万

例如输入:
10 3

程序应该输出:
4

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include
不能通过工程设置而省略常用头文件。

提交程序时,注意选择所期望的语言类型和编译器类型。

思路
约瑟夫环问题的理解可以网上找找,我添加了一些注释可以对照着理解。

#include <iostream>

using namespace std;

int f[1000010];

int main(){
	int n,k;
	cin >> n >> k;
	
	// f[i] 表示从0开始报,有i个人的情况下,最终出局的人为f[i]
	
	// f[1] = 0; 表示从0开始报,有1个人的情况下,最终出局的人为f[1] = 0 (编号从0开始)
	
	// f[i] = (f[i-1]+k) % i; 表示先假设i个人数了k次,编号为k-1的人出局后
	// 当前剩余人数为i-1人,将编号全部减去k后刚好变成了f[i-1]要解决的问题。
	// 因此 f[i] = (f[i-1]+k) % i;
	f[1] = 0; 
	for(int i=2;i<=n;i++) 
		f[i] = (f[i-1]+k) % i;
	
	cout << f[n]+1 << endl;
	
	return 0;
}

自描述序列 – todo

小明在研究一个序列,叫Golomb自描述序列,不妨将其记作{G(n)}。这个序列有2个很有趣的性质:

  1. 对于任意正整数n,n在整个序列中恰好出现G(n)次。
  2. 这个序列是不下降的。

以下是{G(n)}的前几项:

n 1 2 3 4 5 6 7 8 9 10 11 12 13
G(n) 1 2 2 3 3 4 4 4 5 5 5 6 6

给定一个整数n,你能帮小明算出G(n)的值吗?

输入

一个整数n。

对于30%的数据,1 <= n <= 1000000
对于70%的数据,1 <= n <= 1000000000
对于100%的数据,1 <= n <= 2000000000000000

输出

一个整数G(n)

【样例输入】
13

【样例输出】
6

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include
不能通过工程设置而省略常用头文件。

提交程序时,注意选择所期望的语言类型和编译器类型。

思路
要仔细读题,题目意思是比如G(4) = 3,表示有G(6) = G(7) = G(8) = 4,G(x)为4的有三个。

对于30%的数据,1 <= n <= 1000000 时间复杂度控制到O(N)可以过。原始解法:

#include <iostream>

using namespace std;

int G[2000010];

int main(){
	int n; cin >> n;
	G[1] = 1,G[2] = 2;
	for(int i=2,j=1;;i++){
		int num = G[i];
		while(num--){
			G[++j] = i;
			if(j == n){
				cout << G[n] << endl;
				return 0;
			}
		}
	}
	return 0;
}

采油 – todo

LQ公司是世界著名的石油公司,为世界供应优质石油。
最近,LQ公司又在森林里发现了一大片区域的油田,可以在这个油田中开采n个油井。
LQ公司在这n个油井之间修建了n-1条道路,每条道路连接两个油井,路径中间不会路过任何油井,而且这些道路将所有油井连通。
建立油井的时候需要使用一台大型设备,运输起来非常麻烦,LQ公司准备在其中的一个油井位置建立一个空运站,先将设备空运到空运站,之后每次经过他们建立的道路来运输这个大型设备以建立不同的油井,当油井建立完毕后再从空运站将大型设备运走。
为了减少运输的麻烦,公司要求大型设备在道路上运输的总路程是最短的。

在建立油井和采油的过程中需要花费一些人力,第i个油井需要花费Bi个人,而一旦油井建成,就需要Si个人一直坚守在油井上进行维护。
当然,如果一个人参与了油井的建设,他可以直接留下来维护油井,或者参与下一个油井的建设,但是在维护油井的人不能再参加后续油井的建设了。

现在LQ公司想知道,大型设备运输的总路径长度最短是多少?在保证总路径长度最短的情况下,LQ公司至少需要花费多少人力才能完成所有油井的建立与维护。

【输入格式】
  输入的第一行包含一个整数n,表示油井的数量。油井由1到n依次标号。
  第二行包含n个整数,依次表示B1, B2, …, Bn,相邻的整数之间用一个空格分隔。
  第三行包含n个整数,依次表示S1, S2, …, Sn,相邻的整数之间用一个空格分隔。
  接下来n-1行描述油井之间的道路,其中的第i行包含两个整数a,b,用一个空格分隔,表示一条道路的起点为i+1、终点为a,长度为b,道路是双向的,设备可以从任意一端运送到另一端,每条道路都可以经过任意多次。数据保证任意两个油井之间都可以通过道路连接。

【输出格式】
  输出包含两个整数,用一个空格分隔,表示最优情况下大型设备需要运输的总路程,以及在总路程最短的情况下最少需要花费的人力数量。

【样例输入】
2
10 20
15 15
1 8

【样例输出】
16 30

【样例说明】
  有两种方案达到最优。
  方案一:在油井2建立空运站,先建立油井2,再将大型设备运输到油井1建立油井1,最后将大型设备运回油井2。
  方案二:在油井1建立空运站,先将大型设备运输到油井2建立油井2,再将大型设备运送到油井1建立油井1。

【样例输入】
6
3 10 20 7 15 9
2 6 10 4 8 7
1 9
1 2
2 5
3 4
3 7

【样例输出】
54 38

【数据规模和约定】
  对于20%的数据:n不超过10;
  另外20%的数据:每个油井最多和两个油井之间有道路直接连接;
  另外10%的数据:有n-1个油井只有一条道路与其他油井连接;
  对于100%的数据:n不超过100000,B、S、c均为不超过10000的正整数。

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include
不能通过工程设置而省略常用头文件。

提交程序时,注意选择所期望的语言类型和编译器类型。

国赛C++ B组

换零钞

x星球的钞票的面额只有:100元,5元,2元,1元,共4种。
小明去x星旅游,他手里只有2张100元的x星币,太不方便,恰好路过x星银行就去换零钱。
小明有点强迫症,他坚持要求200元换出的零钞中2元的张数刚好是1元的张数的10倍,
剩下的当然都是5元面额的。

银行的工作人员有点为难,你能帮助算出:在满足小明要求的前提下,最少要换给他多少张钞票吗?
(5元,2元,1元面额的必须都有,不能是0)

注意,需要提交的是一个整数,不要填写任何多余的内容。

思路
送分题,答案是5+50+19 = 74。

#include<iostream>
using namespace std;

int main(){
	for(int x=1;x<1000;x++){
		int y = 10*x;
		int z = (200-x-y*2)/5;
		if(z>0 && (200-x-y*2)%5==0){
			cout << x << " " << y << " " << z << endl;
		} 
	}
	return 0;
}

激光样式

x星球的盛大节日为增加气氛,用30台机光器一字排开,向太空中打出光柱。
安装调试的时候才发现,不知什么原因,相邻的两台激光器不能同时打开!
国王很想知道,在目前这种bug存在的情况下,一共能打出多少种激光效果?

显然,如果只有3台机器,一共可以成5种样式,即:
全都关上(sorry, 此时无声胜有声,这也算一种)
开一台,共3种
开两台,只1种

30台就不好算了,国王只好请你帮忙了。

要求提交一个整数,表示30台激光器能形成的样式种数。

注意,只提交一个整数,不要填写任何多余的内容。

思路
比较基础的递归题。答案是2178309。

#include<iostream>
#include<string>

using namespace std;

int res = 0;

void dfs(string str){
	if(str.size() == 30){
		// cout << str << endl;
		res++;
		return;
	}
	
	if(str.size() == 0 || str[str.size()-1] == '0'){
	 	dfs(str+'1');
	 	dfs(str+'0');
	}
	else dfs(str+'0');
}

int main(){
	dfs("");
	cout << res << endl; 
	return 0;
}

格雷码

格雷码是以n位的二进制来表示数。
与普通的二进制表示不同的是,它要求相邻两个数字只能有1个数位不同。
首尾两个数字也要求只有1位之差。

有很多算法来生成格雷码。以下是较常见的一种:
从编码全0开始生成。
当产生第奇数个数时,只把当前数字最末位改变(0变1,1变0)
当产生第偶数个数时,先找到最右边的一个1,把它左边的数字改变。
用这个规则产生的4位格雷码序列如下:
0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

以下是实现代码,仔细分析其中逻辑,并填写划线部分缺少的代码。

#include <stdio.h>
void show(int a,int n)
{
	int i;
	int msk = 1;
	for(i=0; i<n-1; i++) msk = msk << 1;
	for(i=0; i<n; i++){
		printf((a & msk)? "1" : "0");
		msk = msk >> 1;
	}
	printf("\n");
} 

void f(int n)
{
	int i;
	int num = 1;
	for(i=0; i<n; i++) num = num<<1;
	
	int a = 0;
	for(i=0; i<num; i++){
		show(a,n);
		
		if(i%2==0){
			a = a ^ 1;
		}
		else{
			a = _________________________ ; //填空
		}
	}
}

int main()
{
	f(4);
	return 0;
}

请注意:只需要填写划线部分缺少的内容,不要抄写已有的代码或符号。

思路
熟悉lowbit运算的应该可以很快解决。

先介绍下lowbit运算。
我们假设 a = 0b010100,那么我们如何快速的找到a的最右边的一个1呢?
计算机中补码的表示形式,将-a表示为a取反加1,即 -a = 0b101100,(根据位数负数前面会补1,这里就设为6位),那么-a & a = 0b000100,得到的即为最右边的一个1,于是答案即为a ^ ((-a&a)<<1),表示最右边的一个1的左边一个数字取异或(异或运算某一位为1,另一个数的相应为改变)。

调手表

小明买了块高端大气上档次的电子手表,他正准备调时间呢。
在 M78 星云,时间的计量单位和地球上不同,M78 星云的一个小时有 n 分钟。
大家都知道,手表只有一个按钮可以把当前的数加一。在调分钟的时候,如果当前显示的数是 0 ,那么按一下按钮就会变成 1,再按一次变成 2 。如果当前的数是 n - 1,按一次后会变成 0 。
作为强迫症患者,小明一定要把手表的时间调对。如果手表上的时间比当前时间多1,则要按 n - 1 次加一按钮才能调回正确时间。
小明想,如果手表可以再添加一个按钮,表示把当前的数加 k 该多好啊……
他想知道,如果有了这个 +k 按钮,按照最优策略按键,从任意一个分钟数调到另外任意一个分钟数最多要按多少次。
注意,按 +k 按钮时,如果加k后数字超过n-1,则会对n取模。
比如,n=10, k=6 的时候,假设当前时间是0,连按2次 +k 按钮,则调为2。

「输入格式」
一行两个整数 n, k ,意义如题。

「输出格式」
一行一个整数
表示:按照最优策略按键,从一个时间调到另一个时间最多要按多少次。

「样例输入」
5 3

「样例输出」
2

「样例解释」
如果时间正确则按0次。否则要按的次数和操作系列之间的关系如下:
1:+1
2:+1, +1
3:+3
4:+3, +1

「数据范围」
对于 30% 的数据 0 < k < n <= 5
对于 60% 的数据 0 < k < n <= 100
对于 100% 的数据 0 < k < n <= 100000

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include
不能通过工程设置而省略常用头文件。

提交程序时,注意选择所期望的语言类型和编译器类型。

思路
题意:如果有了这个 +k 按钮,按照最优策略按键,从任意一个分钟数调到另外任意一个分钟数最多要按多少次。因为总共就 n 分钟,所以简化为 n 种情况,需要走0,1,2,n-1 步,其中0步自然是0次,1步是一次,k步是一次等等。

使用BFS,BFS先到达的地方步数最小。

#include<iostream>
#include<cstring>
#include<queue>

using namespace std;

int d[100010];

int main(){
	int n,k;
	cin >> n >> k;
	
	memset(d,-1,sizeof d);
	queue<int> q;
	q.push(0);
	d[0] = 0;
	while(q.size()){
		int it = q.front();
		q.pop();
		
		int a = (it+1)%n;
		int b = (it+k)%n;
		if(d[a] == -1){
			d[a] = d[it]+1;
			q.push(a);
		}
		if(d[b] == -1){
			d[b] = d[it]+1;
			q.push(b);
		}
	}
	
	int res = 0;
	for(int i=1;i<n;i++)
		res = max(res,d[i]);
	cout << res << endl;
	return 0;
}

搭积木 – important

小明对搭积木非常感兴趣。他的积木都是同样大小的正立方体。
在搭积木时,小明选取 m 块积木作为地基,将他们在桌子上一字排开,中间不留空隙,并称其为第0层。
随后,小明可以在上面摆放第1层,第2层,……,最多摆放至第n层。摆放积木必须遵循三条规则:

规则1:每块积木必须紧挨着放置在某一块积木的正上方,与其下一层的积木对齐;
规则2:同一层中的积木必须连续摆放,中间不能留有空隙;
规则3:小明不喜欢的位置不能放置积木。

其中,小明不喜欢的位置都被标在了图纸上。图纸共有n行,从下至上的每一行分别对应积木的第1层至第n层。每一行都有m个字符,字符可能是‘.’或‘X’,其中‘X’表示这个位置是小明不喜欢的。
现在,小明想要知道,共有多少种放置积木的方案。他找到了参加蓝桥杯的你来帮他计算这个答案。
由于这个答案可能很大,你只需要回答这个答案对1000000007(十亿零七)取模后的结果。
注意:地基上什么都不放,也算作是方案之一种。

【输入格式】
输入数据的第一行有两个正整数n和m,表示图纸的大小。
随后n行,每行有m个字符,用来描述图纸 。每个字符只可能是‘.’或‘X’。

【输出格式】
输出一个整数,表示答案对1000000007取模后的结果。

【样例输入1】

2 3
..X
.X.

【样例输出1】
4

【样例说明1】
成功的摆放有(其中O表示放置积木):

(1)
..X
.X.
(2)
..X
OX.
(3)
O.X
OX.
(4)
..X
.XO

【样例输入2】

3 3
..X
.X.
...

【样例输出2】
16

【数据规模约定】
对于10%的数据,n=1,m<=30;
对于40%的数据,n<=10,m<=30;
对于100%的数据,n<=100,m<=100。

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include
不能通过工程设置而省略常用头文件。

提交程序时,注意选择所期望的语言类型和编译器类型。

思路

自己写的直接根据题意的DFS,发现太慢了。

#include<iostream>
#include<string>
#include<vector>
#include<set>

using namespace std;

set<vector<string> > st;
vector<string> strv; 
int n,m,res = 0;

void dfs(int row,vector<string>& strNow){
	if(row == -1){
		if(st.count(strNow)) return;
		
		st.insert(strNow);
		res = (res+1)%1000000007;	
		// for(int i=0;i<n;i++)	
		//	cout << strNow[i] << endl;
		// cout << endl; 
		return;
	}
	
	// 规则2:同一层中的积木必须连续摆放,中间不能留有空隙;
	bool begin = false;
	for(int i=0;i<m;i++){
		if(strNow[row][i] == 'O'){
			if(begin) return;
			begin = true;
		}
		while(strNow[row][i] == 'O') i++;
	}
	
	for(int i=0;i<m;i++){
		// 规则3:小明不喜欢的位置不能放置积木。
		if(strNow[row][i] == 'X' || strNow[row][i] == 'O') continue;
		
		// 规则1:每块积木必须紧挨着放置在某一块积木的正上方,与其下一层的积木对齐;
		if(row+1<n && strNow[row+1][i] != 'O') continue;
		
		strNow[row][i] = 'O';
		dfs(row,strNow);
		strNow[row][i] = '.';
	}
	
	dfs(row-1,strNow);
}

int main(){
	cin >> n >> m;
	for(int i=0;i<n;i++){
		string str;
		cin >> str;
		strv.push_back(str);	
	} 
	
	dfs(n-1,strv);	
	cout << res << endl;
	return 0;
}

前缀和思想加暴力dp太秀了,这个代码的时间复杂度为O(n*m^4),大概运算可以控制在10^7内,也就是可以过40%的数据,因为要过100%的数据需要优化dp,那样太难想到了 <(_ _)>。希望这个代码可以帮助读者理解前缀和思想加暴力dp解决问题。

#include<iostream>
using namespace std;
 
int n,m,res = 0;
long long dp[105][105][105]; // dp i j k 表示i行,[j,k]区间的数目 
int g[105][105];

int main(){
	cin >> n >> m;
	
	char str[105];
	for(int i=n;i>=1;i--){
		cin >> str+1;	
		for(int j=1;j<=m;j++){ // 前缀和思想,O(1)查找区间是否有X 
			g[i][j] = g[i][j-1];
			if(str[j] == 'X') g[i][j]++;
		}
	}
	
	int res = 1; // 表示什么都不放
	 
	for(int i=1;i<=m;i++) // 初始化最底层,如果区间没有X就表示一种情况(上面的层不做处理) 
		for(int j=i;j<=m;j++){
			if(g[1][j] - g[1][i-1]) continue;
			dp[1][i][j] = 1;
			res++;
		} 
 
	for(int i=2;i<=n;i++) // 逐层递增(i以上的层不做处理) 
		for(int j=1;j<=m;j++)
			for(int k=j;k<=m;k++){
				if(g[i][k] - g[i][j-1]) continue;
				// 如果区间没有X,则可能情况为下面的一层按照规则加上。 
				for(int x=1;x<=j;x++)
					for(int y=k;y<=m;y++)
						dp[i][j][k] += dp[i-1][x][y];
				res = (res+dp[i][j][k])%1000000007;
			}
					
	cout << res << endl;
	return 0;
}

矩阵求和 – important

经过重重笔试面试的考验,小明成功进入 Macrohard 公司工作。
今天小明的任务是填满这么一张表:
表有 n 行 n 列,行和列的编号都从1算起。
其中第 i 行第 j 个元素的值是 gcd(i, j)的平方,
gcd 表示最大公约数,以下是这个表的前四行的前四列:
1 1 1 1
1 4 1 4
1 1 9 1
1 4 1 16

小明突然冒出一个奇怪的想法,他想知道这张表中所有元素的和。
由于表过于庞大,他希望借助计算机的力量。

「输入格式」
一行一个正整数 n 意义见题。

「输出格式」
一行一个数,表示所有元素的和。由于答案比较大,请输出模 (10^9 + 7)(即:十亿零七) 后的结果。

「样例输入」
4

「样例输出」
48

「数据范围」
对于 30% 的数据,n <= 1000
存在 10% 的数据,n = 10^5
对于 60% 的数据,n <= 10^6
对于 100% 的数据,n <= 10^7

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 2000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include
不能通过工程设置而省略常用头文件。

提交程序时,注意选择所期望的语言类型和编译器类型。

思路
直接gcd的方法,随便复习线性筛法 O(N)得到素数。如果都为素数则直接为1,否则用gcd(貌似和直接gcd区别不大)。过 30% 的数据还是没问题的。

#include<iostream>
#include<cstring>
using namespace std;

const int modn = 1e9 + 7;

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

int v[1000010],prime[1000010],m;
void primes(int n){
	memset(v,0,sizeof v);
	m = 0;
	for(int i=2;i<=n;i++){
		if(v[i] == 0) {
			v[i] = i;
			prime[++m] = i;
		}
		
		for(int j=1;j<=m;j++){
			// i有比prime[j]更小的质因子
			if(prime[j] > v[i] || prime[j] > n/i) break;
			// v[] 最小质因子 
			v[i*prime[j]] = prime[j];
		}
	}
}

int main(){
	int n;
	cin >> n;
	primes(n);
	 
	int res = 2*n-1; // 第1行,第1列的和 
	for(int i=2;i<=n;i++) res += i*i; // 对角线和 
	int temp = 0;
	for(int i=3;i<=n;i++){
		for(int j=2;j<i;j++){
			if(v[i] == 0 && v[j] == 0)
				temp++;
			else{
				int t = gcd(i,j);
				temp += t*t;
			}
		}	
	}
	cout << (res+temp*2)%modn << endl;
	return 0;
}

这道题的正确解法需要知道欧拉函数

参考博客: https://blog.csdn.net/weixin_43237242/article/details/97388834

欧拉函数:就是对于一个正整数n,小于n且和n互质的正整数(包括1)的个数,记作φ(n) 。

欧拉函数的通式:φ(n)=n*(1-1/p1)(1-1/p2)(1-1/p3)*(1-1/p4)……(1-1/pn),其中p1, p2……pn为n的所有质因数,n是不为0的整数。φ(1)=1。

O(NlogN) 求欧拉函数和素数,可以用线性筛选法的思想将复杂度降到 O(N),不过这种写法很简便。

#include<iostream>
using namespace std;

const int maxn = 110;
int E[maxn],prime[maxn];

// O(NlogN) 求欧拉函数和素数 
void euler(){
	int m = 0;
	E[1] = 1;
	for(int i=2;i<maxn;i++){
		if(E[i]) continue;
		prime[++m] = i;
		for(int j=i;j<maxn;j+=i){
			if(E[j] == 0) E[j] = j;
			E[j] = E[j]/i*(i-1);
		}
	}
	
	for(int i=1;i<maxn;i++) cout << E[i] << " ";
	cout << endl; 
	for(int i=1;i<=m;i++) cout << prime[i] << " ";
}

int main(){
	euler();
	return 0;
}

最终解法,思路写到了代码里面:

#include<iostream>
using namespace std;

const int maxn = 10000010;
const int modn = 1e9 + 7;
int E[maxn];
long long s[maxn];

void euler(int n){
	int m = 0;
	E[1] = 1;
	for(int i=2;i<=n;i++){
		if(E[i]) continue;
		for(int j=i;j<=n;j+=i){
			if(E[j] == 0) E[j] = j;
			E[j] = E[j]/i*(i-1);
		}
	}
}

int main(){
	int n; cin >> n;
	euler(n);
	// s[k] 表示 1<i,j<k 中多少(i,j)是互质的。 
	s[1] = 1; 
	// 看成矩阵扩大1,加上两条边 
	for(int i=2;i<=n;i++) s[i] = s[i-1] + 2*E[i];
	
	long long res = 0;
	// 矩阵中,最大公约数的范围一定也为 [1,n]。
	// 让d表示矩阵的数,s[n/d]表示矩阵中的数出现次数。 
	// 即让d表示最大公约数,s[n/d]即 gcd(i,j)为d的i,j有多少对。
	// 即需要快速求 1<=i,j<=n时,gcd(i,j)为d的i,j有多少对。
	// 即求 1<=i,j<=n/d时, gcd(i,j)为1有多少对。即i,j互质的有多少对
	for(int d=1;d<=n;d++)
		res = (res + s[n/d]*d%modn*d) % modn;
	cout << res << endl;
	
	return 0;
}
发布了39 篇原创文章 · 获赞 79 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_40515692/article/details/104149006