计蒜客 2018 蓝桥杯省赛 B 组模拟赛(五)

A. 结果填空:矩阵求和(3分)

给你一个n×n的矩阵,里面填充 1 到 n×n 。例如当 n 等于 3 的时候,填充的矩阵如下。

1 2 3
4 5 6
7 8 9

现在我们把矩阵中的每条边的中点连起来,这样形成了一个新的矩形,请你计算一下这个新的矩形的覆盖的数字的和。比如,n = 3 的时候矩形覆盖的数字如下。

       2

4     5     6

       8

那么当 n 等于 101 的时候,矩阵和是多少?

我做的时候采取了最直接的模拟。也有很多方法,找规律即可。

最后的正确结果是:26020201

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

#define MAXN 101

int mat[105][105];

int main()
{
	int k = 1;
	for ( int i = 0 ; i < MAXN ; ++ i ) {
		for ( int j = 0 ; j < MAXN ; ++ j ) {
			mat[i][j] = k ++;
		}
	} 
	
	for ( int i = 0 ; i < MAXN/2 ; ++ i ) {
		int p = MAXN/2-i-1, n = MAXN/2+i+1;
		for ( ; p >= 0 ; p --, n ++ ) {
			mat[i][p] = mat[i][n] = 0;
			mat[MAXN-i-1][p] = mat[MAXN-i-1][n] = 0;
		}
	}
	
	for ( int i = 0 ; i < MAXN/2 ; ++ i ) {
		int p = MAXN/2-i-1, n = MAXN/2+i+1;
		for ( ; p >= 0 ; p --, n ++ ) {
			mat[i][p] = mat[i][n] = 0;
		}
	}
	
	long long res = 0;
	for ( int i = 0 ; i < MAXN ; ++ i ) {
		for ( int j = 0 ; j < MAXN ; ++ j ) {
//			cout << mat[i][j] << " ";
//			printf( "%3d", mat[i][j] );
			res += mat[i][j];
		} cout << endl;
	} 
	cout << res << endl;
	return 0;
}


B. 结果填空:素数个数(9分)

用 0,1,2,3 ... 7 这 8 个数组成的所有整数中,质数有多少个(每个数字必须用到且只能用一次)。

提示:以 0 开始的数字是非法数字。

鄙人不才,直接暴力枚举。素数筛表,遍历,检查是否8个数字不重不漏。

正确答案:2668

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

#define MAX 76543215

int a[MAX];
int b[] = {0, 7, 6, 5, 4, 3, 2, 1};
int c[10];

bool ok( int x ) {
	memset( c, 0, sizeof(c) );
	while ( x ) {
		c[x%10]++;
		x /= 10;
	}
	for ( int i = 0 ; i < 8 ; ++ i ) {
		if ( c[i] != 1 ) return false;
	}
	return true;
}

int main()
{
	a[0] = a[1] = 1;
	for ( int i = 2 ; i*i <= MAX ; ++ i ) {
		for ( int j = i*i ; j < MAX ; j += i ) {
			a[j] = 1;
		}
	}
	int res = 0;
	for ( int i = 10234567 ; i <= MAX ; ++ i ) {
		if ( a[i] == 0 && ok(i) ) res ++;
	}
	
	cout << res << endl;
	
	return 0;
}


C. 结果填空:连连看(11分)

连连看是一款非常有意思的游戏。

我们可以把任意两个在图的在边界上的相同的方格一起消掉,比如把两个 44 消掉以后,

每次消掉两个方格的时候,都有会获得一个分数,第 ii次消的分数为 i × 方格的值。比如上面的消法,是第一次消,获得的分数为 1×4=4

请你帮忙最优操作情况下,获得的分数最多为多少。

89.


按照图中匹配方式 2 -> 1 > 1 > 2 > 3 > 4 > 5


D. 代码填空:快速幂(7分)

一个数的整数次幂,是我们在计算中经常用到的,但是怎么可以在 O(log(n)) 的时间内算出结果呢?

代码框中的代码是一种实现,请分析并填写缺失的代码,求 x^y mod p 的结果。

#include <iostream>
using namespace std;

int pw(int x, int y, int p) {
    if (!y) {
        return 1;
    }
    int res = /*在这里填写必要的代码*/;
    if (y & 1) {
        res = res * x % p;
    }
    return res;
}

int main() {
    int x, y, p;
    cin >> x >> y >> p;
    cout << pw(x, y, p) << endl;
    return 0;
}

比较简单的日常算法: pw(x*x, y>>1, p)


E. 代码填空:末尾零的个数(13分)

N! 末尾有多少个 0 呢?

N!=1×2××N

代码框中的代码是一种实现,请分析并填写缺失的代码。

#include <iostream>
using namespace std;
int main() {
    int n, ans = 0;
    cin >> n;
    while (n) {
        ans += /*在这里填写必要的代码*/;
    }
    cout << ans << endl;
    return 0;
}

正确答案:n/=5

这里可能有点不好理解,

首先,要产生一个0 一定是由一个2和一个5相乘。所以若干数相乘一定要找2和5的个数。较小者即是0的个数。

其次,对于一个数n,如果它的有一个因子5则一定有一个因子2,反之则不一定。

所以。对于一个n,看他是5的多少倍,即得出1-n里有多少个数有一个因子5。这个即是阶乘里,有多少个数有一个因子5,

然后把n/5的结果再赋值给n,这时n/5的值就是1-n里有多少个数有因子25,有因子25的应该有2个因子5,但是前面已经算了一次了,所以这里直接加上这些数就好了,同理一直进行加操作即可。

举个例子就是:对于数66,66/5 = 13,即有13个数有因子5。66/25 = 2,即有2个数有因子25。66/125 = 0。结束计算。

所以66的阶乘应该有13+2个0。 


F. 结果填空:藏宝图(16分)

蒜头君得到一张藏宝图。藏宝图是一个 10×10 的方格地图,图上一共有 10 个宝藏。有些方格地形太凶险,不能进入。

整个图只有一个地方可以出入,即是入口也是出口。蒜头君是一个贪心的人,他规划要获得所有宝藏以后才从出口离开。

藏宝图上从一个方格到相邻的上下左右的方格需要 1 天的时间,蒜头君从入口出发,找到所有宝藏以后,回到出口,最少需要多少天。

鄙人不才,直接上手数了下。正确答案是:48.



G. 程序设计:合并数字(15)

蒜头君得到了 n 个数,他想对这些数进行下面这样的操作,选出最左边的相邻的差的绝对值为 1 的两个数,只保留较小的数,删去较大的数,直到没有两个相邻的差的绝对值为 1 的数,问最多可以进行多少次这样的操作?

输入格式

输入第一行为一个整数 n(1n10^5),表示数字的总数

第二行为 n个整数 x1,x2,...,xn(0xi10^9),表示这些数。

输出格式

输出一行,为一个整数,表示蒜头君最多可以进行多少次这样的操作。

样例输入

4
1 2 0 1

样例输出

3

正常思路,直接模拟。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <list>

using namespace std;

list<int> l;

int main()
{
	int n, x;
	scanf( "%d", &n );
	for ( int i = 0 ; i < n ; ++ i ) {
		scanf( "%d", &x );
		l.push_back(x);
	}
	list<int>::iterator prev = l.begin();
	list<int>::iterator tail = l.begin();
	tail ++;
	int res = 0;
	while ( tail != l.end() ) {
		if ( abs((*prev)-(*tail)) == 1 ) {
			res ++;
			if ( (*prev) > (*tail) ) {
				if ( prev != l.begin() ) {
					l.erase(prev--);
				} else {
					l.erase(prev ++);
					tail ++;
				}
			} else {
				list<int>::iterator cnt = tail ++;
				l.erase(cnt);
			}
		} else {
			prev ++;
			tail ++;
		}
	}
	cout << res << endl;
	return 0;
}

H. 程序设计:蒜头君下棋(20分)

蒜头君喜欢下棋。最近它迷上了国际象棋。国际象棋的棋盘可以被当做一个 8×8 的矩阵,棋子被放在格子里面(不是和中国象棋一样放在线上)。

蒜头君特别喜欢国际象棋里面的马,马的移动规则是这样的:横着走两步之后竖着走一步,或者横着走一步之后竖着走两步。例如,一匹马在 (3,3) 的位置,则它可以到达的地方有 (1,2)(2,1)(1,4)(4,1)(5,2)(2,5)(5,4)(4,5) 八个地方。蒜头君想要把整个棋盘都放上马,并且让这些马不能相互攻击(即任何一匹马不能走一步之后就到达另一匹马的位置)。蒜头君当然知道在 8×8 的棋盘上怎么放马,但如果棋盘变为n×m的,蒜头君就不懂了。他希望你来帮忙他计算一下究竟能放多少匹马。

输入格式

共一行,两个整数nm(1n,m1000),代表棋盘一共有 n 行 m 列。

输出格式

输出一个整数,代表棋盘上最多能放的马的数量。

样例输入1

2 4

样例输出1

4

样例输入2

3 4

样例输出2

6

这个题有点技巧。答案很简单。

当棋盘上只有一行时,棋盘上全放上棋子即可。

当棋盘上有两行时,参考如下方法:


即两排放满,两排放空。

其它情况下,就有点考尝试了,仔细观察一份国际象棋的棋盘,


容易发现,棋盘是黑白相间的(废话。)。仔细观察发现,放在白块上的马会跳到黑块上,同样,黑块上的马会跳到白块上。

因此,把所有的马放在同一颜色下即可。

#include <iostream>

using namespace std;

int main()
{
	int n, m;
	cin >> n >> m;
	if ( n < m ) swap(m, n);
	if ( m == 1 ) {
		cout << n << endl;
	} else if ( m == 2 ) {
		cout << n/4*4+min(n%4,2)*2 << endl;
	} else {
		cout << (m*n+1)/2 << endl;
	}
	return 0;
}


I. 程序设计:蒜头君的数轴(25分)

今天蒜头君拿到了一个数轴,上边有 n 个点,但是蒜头君嫌这根数轴不够优美,想要通过加一些点让它变优美,所谓优美是指考虑相邻两个点的距离,最多只有一对点的距离与其它的不同。

蒜头君想知道,他最少需要加多少个点使这个数轴变优美。

输入格式

输入第一行为一个整数 n(1n105),表示数轴上的点数。

第二行为 n 个不重复的整数 x1,x2,...,xn(10^9xi10^9),表示这些点的坐标,点坐标乱序排列。

输出格式

输出一行,为一个整数,表示蒜头君最少需要加多少个点使这个数轴变优美。

样例输入

4
1 3 7 15

样例输出

1

先考虑个问题,两对相邻的点距,怎么加点会让所有间距都相同,

这个方法可能不好想,但是反过来,怎么让两端点距分隔成若干相同长度的。

这个就相对好找些了,这个答案就是两段距离的最大公约数。

那么要是有若干段加点,使间距相同呢?同样是这个若干段间距的最大公约数。

但是还可以有一段不一样的啊?那就可以枚举不同的那一段,对剩下的求gcd。

那么怎么快速求出这个gcd的值呢?其实可以运用前缀和的思路。

预处理出前缀gcd和后缀gcd,其实也就是前i个数的gcd,和后i个数的gcd。

因此当排除不同的间距 i 时,剩下点距的最大公约数就是gcd( prev[i-1], next[i+1] ) 

这里prev next即前缀gcd和后缀gcd

然后得知那个相同间距了怎么算要加多少个点呢?

把数轴长度求出来,即 len = a[n]-a[1]。然后减去不同的那一段。即 len - dist[i]。

即得出要划分的距离和,然后除以这个间距,即得出要划分成多少段,

再减去原来的段数 n-2,即得出要加顶点数。


#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int gcd( int a, int b ) {
	while ( a && b ) a>b?a%=b:b%=a;
	return a+b;
}

int a[100010];
int dist[100010];
int pre[100010], nex[100010];

int main()
{
	int n;
	scanf( "%d", &n );
	for ( int i = 0 ; i < n ; ++ i ) {
		scanf( "%d", &a[i] );
	}
	if ( n <= 3 ) {
		puts( "0" );
		return 0;
	}
	sort( a, a+n );
	int len = a[n-1]-a[0];
	for ( int i = 0 ; i < n-1 ; ++ i ) {
		// dist[i] 为点i~i+1的距离  共有n-2段 0 ~ n-2 
		dist[i] = a[i+1]-a[i];
	}
	pre[0] = dist[0];
	nex[n-2] = dist[n-2];
	for ( int i = 1 ; i < n-1 ; ++ i ) {
		// 前i段距离的gcd 
		pre[i] = gcd(pre[i-1], dist[i]);
	} 
	for ( int i = n-3 ; i >= 0 ; -- i ) {
		// 后i段距离的gcd 
		nex[i] = gcd(nex[i+1], dist[i]);
	}
	int off = 0, ans = 0x3fffffff;
	// 因为最多可以有一个点距 和其它不同
	// 因此枚举这个点距  用最大距离 减去 这个点距 后 看可以分几段
	// 需要加点的个数  就是这个段数 减去 原来的段数 即 n-2 
	for ( int i = 0 ; i < n-1 ; ++ i ) {
		if ( i == 0 ) off = nex[1];
		else if ( i == n-2 ) off = pre[n-3];
		else {
			off = gcd( pre[i-1], nex[i+1] );
		}
		ans = min(ans, (len-dist[i])/off-n+2);
	}
	cout << ans << endl;
	return 0;
}


J. 程序设计:划分整数(31分)

蒜头君特别喜欢数学。今天,蒜头君突发奇想:如果想要把一个正整数 n 分解成不多于 k 个正整数相加的形式,那么一共有多少种分解的方式呢?

蒜头君觉得这个问题实在是太难了,于是他想让你帮帮忙。

输入格式

共一行,包含两个整数 n(1n300) 和 k(1k300),含义如题意所示。

输出格式

一个数字,代表所求的方案数。

样例输入

5 3

样例输出

5


动态规划。

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

// 这里不用long long只有18分
long long dp[310][310];

int main() {
    int n, k;
    scanf( "%d%d", &n, &k );
    dp[0][0] = 1;
	for ( int i = 1 ; i <= n ; ++ i ) {
		for ( int j = 1 ; j <= i ; ++ j ) {
			if ( i >= j ) {
				dp[i][j] = dp[i-1][j-1] + dp[i-j][j];
			} else {
				dp[i][j] = dp[i][i];
			}
		}
	}
	long long res = 0;
    for ( int i = 1 ; i <= k ; ++ i ) {
    	res += dp[n][i];
    }
    cout << res << endl;
    return 0;
}






猜你喜欢

转载自blog.csdn.net/hopygreat/article/details/79689533
今日推荐