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(1≤n≤10^5),表示数字的总数
第二行为 n个整数 x1,x2,...,xn(0≤xi≤10^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的,蒜头君就不懂了。他希望你来帮忙他计算一下究竟能放多少匹马。
输入格式
共一行,两个整数n和m(1≤n,m≤1000),代表棋盘一共有 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(1≤n≤105),表示数轴上的点数。
第二行为 n 个不重复的整数 x1,x2,...,xn(−10^9≤xi≤10^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(1≤n≤300) 和 k(1≤k≤300),含义如题意所示。
输出格式
一个数字,代表所求的方案数。
样例输入
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; }