本题解非官方满分题解,因此,可能存在下列问题
题意理解错误,导致答案错误。
代码中存在一些问题,导致答案错误。
算法复杂度的分析有误,导致不能在规定时间内得出结果。
由于水平受限,本篇题解全部由 C++
语言完成。
题解中的所有程序在 GNU G++(5.4)
下成功编译并通过所有的测试样例及一定的自造数据。
如有出错(很有可能)欢迎同学指正,祝同学们在正赛中取得好成绩。
问题描述
请问2019的二进制表示是多少?注意在提交的时候不要提交前导0。
题解
数据在计算机中都是以二进制存储,令一整型变量的值为 (2019)10 ,不断输出其最后一个二进制位的值并右移,直到其值为0。这样就得到了逆序的二进制形式,由于数位较少,直接反向输入即可。
答案:11111100011
#include
using namespace std;
int main()
{
int num = 2019;
while(num)
{
printf("%d", num & 1);
num >>= 1;
}
return 0;
}
问题描述
请问十六进制数1949对应的十进制数是多少?请特别注意给定的是十六进制,求的是十进制。
题解
相似的,可以令一个整型变量的值为 (1949)16,然后以十进制格式输出即可。
答案:6473
#include
using namespace std;
int main()
{
int num = 0x1949;
printf("%d", num);
return 0;
}
问题描述
在1至2019中,有多少个数的数位中包括数字9?
注意,有的数中的数位中包含多个9,这个数只算一次。例如,1999这个数包含三个数位9,但在计算时只算做一个数。
题解
枚举从1到2019中的每个数字,并检查其任一数位中是否是9,同时维护一个变量来统计个数即可。
答案:544
#include
using namespace std;
const int maxn = 2019;
int check(int i)
{
while(i)
{
if(i % 10 == 9)
return 1;
i /= 10;
}
return 0;
}
int main()
{
int ans = 0;
for(int i = 1; i <= maxn; i++)
if(check(i))
ans++;
printf("%d", ans);
return 0;
}
问题描述
在一个8x8的国际象棋棋盘的左上角放一枚棋子,每一步只允许棋子向右或向下移动一格子,不允许移出棋盘,14步后可移动到棋盘的右下角。
请问本质不同的移动方案有多少种?
两个移动方案称为本质不同的,是指着两种方案中至少存在某一步,一种方案中是向右走,一种方案中是向下走。
题解
将移动方向视作选项,由题意得:14步中必有7步为向下走、7步为向右走。则本质不同的方案数实际上是在十四个相互独立的选项中选七个。由组合原理可得答案为
C 14 7 = 3432 C_{14}^{7} = 3432 C147=3432
使用动态规划解法,令 d[i][j]
为 (i,j)
处可能的方案种数 ,则
//init
d[1][1] = 1;
d[1][j] = d[1][j - 1];
d[i][1] = d[i - 1][j];
//cal
d[i][j] = d[i-1][j] + d[i][j+1]; //when both i and j are greater than 1;
#include
using namespace std;
const int maxn = 10;
int d[maxn][maxn];
int main()
{
for(int i = 1; i <= 8; i++)
{
for(int j = 1; j <= 8; j++)
{
if(i == 1 && j == 1)
d[i][j] = 1;
else
d[i][j] = d[i-1][j] + d[i][j-1];
}
}
printf("%d", d[8][8]);
return 0;
}
答案:3432
问题描述
在数列 a[1], a[2], …, a[n] 中,如果 a[i] < a[i+1] < a[i+2] < … < a[j],则称 a[i] 至 a[j] 为一段递增序列,长度为 j-i+1。
给定一个数列,请问数列中最长的递增序列有多长。
输入格式
输入的第一行包含一个整数 n。
第二行包含 n 个整数 a[1], a[2], …, a[n],相邻的整数间用空格分隔,表示给定的数列。
输出格式
输出一行包含一个整数,表示答案。
样例输入
7
5 2 4 1 3 7 2
样例输出
3
评测用例规模与约定
对于 50% 的评测用例,2 <= n <= 100,0 <= 数列中的数 <= 1000。
对于所有评测用例,2 <= n <= 1000,0 <= 数列中的数 <= 10000。
题解
设定一个计数器并初始化为1,遍历序列,比较当前元素与下一个元素
#include
using namespace std;
const int maxn = 1004;
int a[maxn];
int main()
{
int n;
cin>>n;
int ans = 0, temp = 0;
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
for(int i = 0; i < n; i++)
{
if(a[i+1] <= a[i])
{
if(temp > ans)
ans = temp;
temp = 1;
}
else
temp++;
}
printf("%d", ans);
return 0;
}
问题描述
输入格式
输入一行,包含一个单词,单词中只包含小写英文字母。
输出格式
输出一行包含一个字母,为单词中第一个出现的元素字母。若单词中不存在元音字母,输出字母n。
样例输入
hello
样例输出
e
样例输入
fly
样例输出
n
评测用例规模与约定
对于所有评测用例,单词中的字母个数不超过100。
题解
题目有误,输出格式说明 中的
为单词中第一个出现的元素字母
应改为
为单词中第一个出现的元音字母
直接遍历字符串,遇到元音字母直接返回该字母,若遍历到字符串结尾,返回字母 n
。
#include
using namespace std;
const int maxn = 103;
char s[maxn];
char work()
{
int len = strlen(s);
for(int i = 0; i < len; i++)
switch(s[i])
{
case 'a':return s[i];
case 'e':return s[i];
case 'i':return s[i];
case 'o':return s[i];
case 'u':return s[i];
}
return 'n';
}
int main()
{
cin>>s;
printf("%c", work());
return 0;
}
问题描述
一般情况下,如果一个单词在段首,则第一个字母大写,后面的字母小写。
给定一个单词,单词中可能包含大小写字母,请按第一个字母大写,后面字母小写的方式输出。
输入格式
输入一行,包含一个单词,单词中只包含大写或小写英文字母。
输出格式
输出单词在段首时的形式,第一个字母大写,其他字母小写。
样例输入
LanQiao
样例输出
Lanqiao
样例输入
cUp
样例输出
Cup
评测用例规模与约定
对于所有评测用例,单词中的字母个数不超过100。
题解
直接遍历字符串
#include
using namespace std;
const int maxn = 103;
char s[maxn];
int main()
{
cin>>s;
int len = strlen(s);
for(int i = 0; i < len; i++)
if(i == 0)
{
if(s[i] >= 'a' && s[i] <= 'z')
s[i] += 'A' - 'a';
}
else
if(s[i] >= 'A' && s[i] <= 'Z')
s[i] += 'a' - 'A';
printf("%s", s);
return 0;
}
问题描述
对于一个 n 行 m 列的表格,我们可以使用螺旋的方式给表格依次填上正整数,我们称填好的表格为一个螺旋矩阵。
例如,一个 4 行 5 列的螺旋矩阵如下:
1 2 3 4 5
14 15 16 17 6
13 20 19 18 7
12 11 10 9 8
输入格式
输入的第一行包含两个整数 n, m,分别表示螺旋矩阵的行数和列数。
第二行包含两个整数 r, c,表示要求的行号和列号。
输出格式
输出一个整数,表示螺旋矩阵中第 r 行第 c 列的元素的值。
样例输入
4 5
2 2
样例输出
15
评测用例规模与约定
对于 30% 的评测用例,2 <= n, m <= 20。
对于 70% 的评测用例,2 <= n, m <= 100。
对于所有评测用例,2 <= n, m <= 1000,1 <= r <= n,1 <= c <= m。
题解
观察样例得到,填数的策略是:
首先按优先级顺序书写好四条while语句,代表往四个方向填数。
while(y + 1 < m && !a[x][y+1]) a[x][++y] = ++tot; //right
while(x + 1 < n && !a[x+1][y]) a[++x][y] = ++tot; //down
while(y - 1 >= 0 && !a[x][y-1]) a[x][--y] = ++tot; //left
while(x - 1 >= 0 && !a[x-1][y]) a[--x][y] = ++tot; //up
然后在外层判断是否已经填满整个矩阵。
#include
using namespace std;
const int maxn = 1004;
int a[maxn][maxn];
int main()
{
int n, m;
int r, c;
cin>>n>>m>>r>>c;
int num = n * m;
int x = 0, y = 0, tot = 1;
a[x][y] = 1;
while(tot < num)
{
while(y + 1 < m && !a[x][y+1]) a[x][++y] = ++tot; //right
while(x + 1 < n && !a[x+1][y]) a[++x][y] = ++tot; //down
while(y - 1 >= 0 && !a[x][y-1]) a[x][--y] = ++tot; //left
while(x - 1 >= 0 && !a[x-1][y]) a[--x][y] = ++tot; //up
}
/*
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
printf("%4d", a[i][j]);
printf("\n");
}
*/
printf("%d", a[r-1][c-1]);
return 0;
}
U P D {UPD} UPD : 题解中的 x , y {x,y} x,y 在二维数组中的使用与坐标轴中是相反的,请读者注意甄别。
问题描述
问题描述
小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。
小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。
这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,这四小块空地都将变为有草的小块。
请告诉小明,k 个月后空地上哪些地方有草。
输入格式
输入的第一行包含两个整数 n, m。
接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。
接下来包含一个整数 k。
输出格式
输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。
样例输入
4 5
.g…
…
…g…
…
2
样例输出
gggg.
gggg.
ggggg
.ggg.
评测用例规模与约定
对于 30% 的评测用例,2 <= n, m <= 20。
对于 70% 的评测用例,2 <= n, m <= 100。
对于所有评测用例,2 <= n, m <= 1000,1 <= k <= 1000。
题解
遍历k次矩阵,在每一个当前是字符 g
的上下左右处做上标记,每次遍历结束后将标记处改为字符 g
即可。
篇幅受限,代码不再给出。时间复杂度为 Θ ( 4 k n m ) { \Theta { \left( {4knm} \right) }} Θ(4knm),可以通过70%的测评用例。
考虑BFS(广度优先遍历)做法。
首先构造一个结构体,其中有如下属性
某个坐标的x值
某个坐标的y值
该坐标长草的天数
队列的初始化方式是:遍历一次矩阵,将第0天就存在的草的坐标和天数0入队。
不断将队头出队,且将其对应的坐标处字符修改为 g
,然后判断天数是否与k相等
若不相等,则将该坐标的上下左右坐标均入队,天数为队头天数+1;
若相等,代表该处的草已是第 k {k} k 月长出,什么也不做。
直到队列为空。
令图中初始的 g
数量为 c {c} c,则复杂度为 Θ ( n m + m i n ( n m , ( 4 c ) k ) ) { \Theta { \left( {nm+min{ \left( {nm,\mathop{{{ \left( {4c} \right) }}}\nolimits^{{k}}} \right) }} \right) }} Θ(nm+min(nm,(4c)k)),可以通过所有测评用例。
#include
using namespace std;
const int maxn = 1004;
char a[maxn][maxn];
typedef struct
{
int x;
int y;
int cnt;
} Node;
int main()
{
int n, m;
int k;
cin>>n>>m;
for(int i = 0; i < n; i++)
cin>>a[i];
cin>>k;
queue<Node> q;
Node temp;
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
if(a[i][j] == 'g')
{
temp.x = i, temp.y = j, temp.cnt = 0;
q.push(temp);
}
while(!q.empty())
{
Node mod = q.front();
a[mod.x][mod.y] = 'g';
q.pop();
if(mod.cnt != k)
{
for(int x = -1; x <= 1; x++)
for(int y = -1; y <= 1; y++)
if(!x || !y)
{
temp.x = mod.x + x, temp.y = mod.y + y, temp.cnt = mod.cnt + 1;
if(temp.x < 0 || temp.x >= n || temp.y < 0 || temp.y >= m)
continue;
q.push(temp);
}
}
}
for(int i = 0; i <= n; i++)
puts(a[i]);
return 0;
}
问题描述
小明要组织一台晚会,总共准备了 n 个节目。然后晚会的时间有限,他只能最终选择其中的 m 个节目。
这 n 个节目是按照小明设想的顺序给定的,顺序不能改变。
小明发现,观众对于晚上的喜欢程度与前几个节目的好看程度有非常大的关系,他希望选出的第一个节目尽可能好看,在此前提下希望第二个节目尽可能好看,依次类推。
小明给每个节目定义了一个好看值,请你帮助小明选择出 m 个节目,满足他的要求。
输入格式
输入的第一行包含两个整数 n, m ,表示节目的数量和要选择的数量。
第二行包含 n 个整数,依次为每个节目的好看值。
输出格式
输出一行包含 m 个整数,为选出的节目的好看值。
样例输入
5 3
3 1 2 5 4
样例输出
3 5 4
样例说明
选择了第1, 4, 5个节目。
评测用例规模与约定
对于 30% 的评测用例,1 <= n <= 20;
对于 60% 的评测用例,1 <= n <= 100;
对于所有评测用例,1 <= n <= 100000,0 <= 节目的好看值 <= 100000。
题解
令每个节目的好看程度依次为 a 1 , a 2 , . . . , a n {\mathop{{a}}\nolimits_{{1}},\text{ }\mathop{{{a}}}\nolimits_{{2}},\text{ }...,\text{ }\mathop{{{a}}}\nolimits_{{n}}} a1, a2, ..., an ,余下还需要选择的节目数量为 c {c} c,则
直到 ( n − c ) − i = = c {(n-c)-i == c} (n−c)−i==c 为止(即当剩余可选区间长度与还需选择的数量相等时)。
时间复杂度分析:
总的时间复杂度等于遍历区间的长度乘以遍历的区间个数。在最糟糕的情况下,需要遍历的区间个数与 m {m} m 相等,需要遍历的区间长度为 n − m {n-m} n−m ,即 O ( m ( n − m ) ) { {\rm O} { \left( {m{ \left( {n-m} \right) }} \right) }} O(m(n−m)) 。可以通过60%的数据。
实际上,该问题是一个求静态区间最值 (static RMQ) 问题,使用任何一种能在 O ( log 2 n ) { {\rm O} { \left( {{{\mathop{{ \text{log} }}\nolimits_{{2}}n}}} \right) }} O(log2n) 时间内求出长度为 n {n} n 的区间最值的数据结构都可以使本题有一个 O ( m log 2 ( n − m ) ) { {\rm O} { \left( {{{m\mathop{{ \text{log} }}\nolimits_{{2}} \left( n-m \right) }}} \right) }} O(mlog2(n−m)) 的算法复杂度上界,从而能够拿到满分。
可以实现满分做法的数据结构有:
Sparse Table(ST表)
Segment Tree(线段树/区间树)
Binary Indexed Tree(树状数组/二叉索引树)
算法有:
该题完成方式多样,编码难度较高(满分),已超出省赛获奖考察范围,在此便不再提供题解代码。各位同学可以尝试自己写出60分的做法,然后在学习上述数据结构或算法后再行完成满分做法。
问题描述
两个二进制数11110011101和1111101001的和是多少?请用二进制表示,注意在提交的时候不要提交前导0。
题解
数据比较小,直接笔算即可。
答案:101110000110
问题描述
1200000有多少个约数(只计算正约数)。
题解
枚举从1到1200000的数字,依次判断其是否是1200000的约数,同时维护一个变量来统计个数即可。
答案:96
#include
using namespace std;
int main()
{
int a = 1200000;
int ans = 0;
for(int i = 1; i <= a; i++)
if(a % i == 0)
ans++;
printf("%d", ans);
return 0;
}
问题描述
一个包含有2019节点的有向图,最多包含多少条边?(不允许有重边)
题解
对于一个无向图,其完全图边数量的计算方式是 C n 2 {C_{n}^{2}} Cn2 ,其含义是在所有节点中选出各自独立的、不重复的两个顶点并连边。而对于有向图,将无向边视作两条有向边即可,答案为 2 ∗ C n 2 {2*C_{n}^{2}} 2∗Cn2 。
答案:4074342
见:周日上午 F
问题描述
在数列 a_1, a_2, …, a_n中,如果 a_i 和 a_j 满足 i < j 且 a_i > a_j, 则称为一个逆序数对。
给定一个数列,请问数列中总共有多少个逆序对。
输入格式
输入的第一行包含一个整数n。
第二行包含n个整数 a_1, a_2, …, a_n,相邻的整数间用空格分隔, 表示给定的数列。
输出格式
输出一行包含一个整数,表示答案。
样例输入
6
3 1 5 2 3 5
评测用例规模与约定
对于 50% 的评测用例,1 <= n <= 100,0 <= 数列中的数 <= 1000。
对于所有评测用例,1 <= n <= 1000,0 <= 数列中的数 <= 10000。
题解
对于除 a 1 {\mathop{{a}}\nolimits_{{1}}} a1 以外的每一个 a i {\mathop{{a}}\nolimits_{{i}}} ai,遍历 a 1 {\mathop{{a}}\nolimits_{{1}}} a1 到 a i − 1 {\mathop{{a}}\nolimits_{{i-1}}} ai−1检查其是否大于 a i {\mathop{{a}}\nolimits_{{i}}} ai,并维护一个变量来统计个数。
#include
using namespace std;
const int maxn = 1004;
int a[maxn];
int main()
{
int n;
cin>>n;
int ans = 0;
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
for(int i = 1; i < n; i++)
for(int j = i - 1; j >= 0; j--)
if(a[j] > a[i])
ans++;
printf("%d", ans);
return 0;
}
问题描述
小明非常不喜欢数字 2,包括那些数位上包含数字 2 的数。如果一个数的数位不包含数字 2,小明将它称为洁净数。
请问在整数 1 至 n 中, 洁净数有多少个?
输入格式
输入的第一行包含一个整数 n。
输出格式
输出一行包含一个整数,表示答案。
输入的第一行包含一个整数n。
第二行包含n个整数 a_1, a_2, …, a_n,相邻的整数间用空格分隔, 表示给定的数列。
输出格式
输出一行包含一个整数,表示答案。
样例输入
30
样例输出
18
题解
枚举从1到n中的每个数字,并检查其任一数位是否是2,同时维护一个变量来统计个数即可。
#include
using namespace std;
int check(int i)
{
while(i)
{
if(i % 10 == 2)
return 0;
i /= 10;
}
return 1;
}
int main()
{
int n;
cin>>n;
int ans = 0;
for(int i = 1; i <= n; i++)
if(check(i))
ans++;
printf("%d", ans);
return 0;
}
问题描述
小明每天都要练功,练功中的重要的一项是梅花桩。
小明练功的梅花桩排列成 n 行 m 列, 相邻两行的距离为 1,相邻两列的距离也为 1。
小明站在第 1 行第 1 列上,他要走到第 n 行第 m 列上。小明已经练了一段时间,他现在可以一步移动不超过d的距离(直线距离)。
小明想知道,在不掉下梅花桩的情况下,自己最少要多少步可以移动到目标。
输入格式
输入的第一行包含两个整数 n, m,分别表示梅花桩的行数和列数。
第二行包含一个实数 d(最多包含一位小数),表示小明一步可以移动的距离。
输出格式
输出一个整数,表示小明最少多少步可以到达目标。
样例输入
3 4
1.5
样例输出
3
评测用例规模与约定
对于 30% 的评测用例,2 <= n, m <= 20,1 <= d <= 20。
对于 60% 的评测用例,2 <= n, m <= 100,1 <= d <= 100。
对于所有评测用例,2 <= n, m <= 1000,1 <= d <= 100。
题解
考虑BFS(广度优先遍历)做法。
将左上角坐标(1,1)入队,并在令 d[1][1]
处为0。不断将队头出队,并将与队头坐标 ( i , j ) {(i,j)} (i,j) 距离 d i s < d {dis < d} dis<d 的所有坐标入队,并将其坐标对应的 d[x][y]
标记为 d[i][j]+1
,直到队列为空。
此时,d[n][m]
处即为答案。
#include
using namespace std;
const int N = 1004;
int dis[N][N];
int n,m;
double d;
queue< pair<int,int> >q;
void bfs()
{
q.push(make_pair(1, 1));
while(!q.empty())
{
pair<int,int> t = q.front();
q.pop();
int x = t.first, y = t.second;
int tx = x, ty = y + (int)d;
if((n-x)*(n-x) + (m-y)*(m-y) <= d*d)
{
dis[n][m] = dis[x][y] + 1;
break;
}
while(tx <= n && ty >= y && ty <= m)
{
if((tx - x) * (tx - x) + (ty - y) * (ty - y) <= d * d && dis[tx][ty] == 0)
{
q.push(make_pair(tx, ty));
dis[tx][ty] = dis[x][y] + 1;
tx ++;
}
else ty --;
}
}
cout<<dis[n][m]<<endl;
}
int main()
{
cin>>n>>m>>d;
bfs();
return 0;
}
问题描述
如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。即 a[2i] < a[2i-1], a[2i+1] >a[2i]。
小明想知道,长度为m,每个数都是1到n之间的正整数的摆动序列一共有多少个。
输入格式
输入的第一行包含两个整数 n, m。
输出格式
输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
样例输入
3 4
样例输出
14
评测用例规模与约定
对于 20% 的评测用例,1 <= n, m <= 5。
对于 50% 的评测用例,1 <= n, m <= 10。
对于 80% 的评测用例,1 <= n, m <= 100。
对于所有评测用例,1 <= n, m <= 1000。
题解
考虑DFS(深度优先遍历)做法。
使用递归去枚举序列的所有可能性,并逐一判断其是否是一个摆动序列。时间复杂度 Θ ( n m + 1 ) {{ \Theta \left( \mathop{{n}}\nolimits^{{m+1}} \right) }} Θ(nm+1)。
在DFS的过程中,在序列中每个数字选取时都及时判断是否能与前面已选取的数字构成摆动序列,若不能立刻返回。则能够对遍历树进行剪枝。
考虑动态规划做法。
使用一个二维数组 d[n][m]
,其含义代表第 n {n} n 个数选择 m {m} m 时的方案总数。
显然,当 n = 1 {n = 1} n=1 时,d[1][m]
的值均为1。
除此之外
当 n {n} n 为偶数时,d[n][m]
的值显然应该为 ∑ i = m + 1 m a x m d [ n − 1 ] [ i ] {{\mathop{{\mathop{ \sum }\limits_{{i=m+1}}^{{\mathop{{max}}\nolimits_{{m}}}}}}\nolimits_{{}}{d \left[ n-1 \left] \left[ i \right] \right. \right. }}} i=m+1∑maxmd[n−1][i] 。
当 n {n} n 为奇数时,d[n][m]
的值显然应该为 ∑ i = m i n m m − 1 d [ n − 1 ] [ i ] {{\mathop{{\mathop{ \sum }\limits_{{i=\mathop{{min}}\nolimits_{{m}}}}^{{m-1}}}}\nolimits_{{}}{d \left[ n-1 \left] \left[ i \right] \right. \right. }}} i=minm∑m−1d[n−1][i] 。
则 d[maxn][maxm]
为所求。
时间复杂度 O ( n m 2 ) {{ {\rm O} \left( n\mathop{{m}}\nolimits^{{2}} \right) }} O(nm2) ,可以通过80%的评测用例。
显然,在上述80分做法中,有大量的重复计算。
例如,在统计 d[2][1],d[2][2]
时都会重复的计算第二行中大于2的元素。这启发我们去记录之前已经计算过的数据,下次使用时直接读取,从而省掉重复计算的时间。
d[1][j]
为:第 1 {1} 1 个数选择大于等于 j {j} j 的数时的方案总数。d[i][j]
为:第 i {i} i 个数选择大于等于 j {j} j 的数时的方案总数。d[i][j]
为:第 i {i} i 个数选择小于等于 j {j} j 的数时的方案总数。例:在样例中,第一行元素由 { 1 , 1 , 1 , 1 } {{ \left\{ {1,\text{ }1,\text{ }1,\text{ }1} \right\} }} {1, 1, 1, 1} 变为 { 4 , 3 , 2 , 1 } {{ \left\{ {4,\text{ }3,\text{ }2,\text{ }1} \right\} }} {4, 3, 2, 1} 。
此时,在下一行(偶数行)中,某个d[i][j]
便可以直接由d[i-1][j+1],d[i][j-1]
推得。例:d[2][2]
应为d[1][3] + d[2][2]
其含义是:在某个偶数位选择数 j {j} j 时,其可能的方案数应该和其前一位 ( i − 1 {i-1} i−1)选择大于等于 j + 1 {j+1} j+1 的数时的方案总数相等,这恰是d[i][j] = d[i-1][j+1]
,又因为需要维护偶数行性质,实际上d[i][j] = d[i-1][j+1] + d[i][j-1]
。
奇数行中同理,但由于求得小下标处的数据需要用到大下标处的数据进行递推,所以需要逆向递推。
d[i][j] = d[i-1][j-1] + d[i][j+1]
至此,我们已经得到初始状态与递推方程
//init
d[1][n] = 1;
for(int i = n - 1; i > 0; i--)
d[1][i] = d[1][i-1] + 1;
//cal
d[i][j] = d[i-1][j-1] + d[i][j+1]; //when i & 1 == 1
d[i][j] = d[i-1][j+1] + d[i][j-1]; //when i & 1 != 1
则对于奇数长度的序列,d[n][1]
为所求,对于偶数长度的序列, d[n][m]
为所求。
对d数组的一次遍历即可求出所有答案,则时间复杂度降为 O ( n m ) {{ {\rm O} \left( nm \right) }} O(nm) ,可以通过所有评测用例。
#include
using namespace std;
const int MAXN = 1004;
const int MOD = 10000;
int d[MAXN][MAXN];
int main()
{
int m, n;
cin>>m>>n;
for(int i = 1; i <= n; i++)
d[1][i] = n - i + 1;
for(int i = 2; i <= m; i++)
if(i & 1)
for(int j = n; j >= 1; j--)
d[i][j] = (d[i-1][j-1] + d[i][j+1]) % MOD;
else
for(int j = 1; j <= n; j++)
d[i][j] = (d[i-1][j+1] + d[i][j-1]) % MOD;
int ans = m & 1 ? d[m][1] : d[m][n];
printf("%d", ans);
return 0;
}
U P D : {UPD:} UPD: 本篇题解中 n , m {n,m} n,m 的含义与题中相反,但代码中相同,请读者注意甄别。
问题描述
小明和朋友们一起去郊外植树,他们带了一些在自己实验室精心研究出的小树苗。
小明和朋友们一共有 n 个人,他们经过精心挑选,在一块空地上每个人挑选了一个适合植树的位置,总共 n 个。
他们准备把自己带的树苗都植下去。
然而,他们遇到了一个困难:有的树苗比较大,而有的位置挨得太近,导致两棵树植下去之后会撞在一起。
他们将树看成一个圆,圆心在他们找的位置上。如果两棵树对应的圆相交,这两棵树就不适合同时植下(相切不受影响),称为两棵树冲突。
小明和朋友们决定先合计合计,只将其中的一部分树植下去,保证没有相互冲突的数。他们同时希望这些树所能覆盖的面积和(圆面积和)最大。
输入格式
输入的第一行包含一个整数 n, 表示人数,即准备植树的位置数。
接下来的 n 行,每行三个整数 x, y, r,表示一棵树在空地上的横、纵坐标和半径。
输出格式
输出一个整数,表示在不冲突下可以植树的面积和。由于每棵树的面积都是圆周率的整数倍,请输出答案除以圆周率后的值(应当是一个整数)。
样例输入
6
1 1 2
1 4 2
1 7 2
4 1 2
4 4 2
4 7 2
样例输出
12
评测用例规模与约定
对于 30% 的评测用例,1 <= n<= 10;
对于 60% 的评测用例,1 <= n, m <= 10。
对于所有评测用例,1 <= n <= 30, 0 <= x, y <= 1000, 1 <= r <= 1000。
题解
考虑DFS+回溯做法。
枚举出长度为 n {n} n 的二进制01串,第 i {i} i 位上的二进制位代表其是否种植。逐一统计出可种植的情况,并以此维护种植面积的最大值即可。
问题描述
在计算机存储中,15.125GB是多少MB?
题解
1GB = 1024MB。
#include
using namespace std;
int main()
{
double GB = 15.125;
double ans = GB * 1024;
printf("%f", ans);
return 0;
}
答案:15488
见 周六上午 B。
问题描述
由1对括号,可以组成一种合法括号序列:()。
由2对括号,可以组成两种合法括号序列:()()、(())。
由4对括号组成的合法括号序列一共有多少种?
题解
问题规模较小,手动枚举所有可能即可。
实际上,对于任意正整数 n {n} n , n {n} n 对括号可组成合法的序列个数为第 n {n} n 个卡特兰 ( C a t a l a n ) {(Catalan)} (Catalan) 数。有兴趣的同学可以自行查阅资料并证明。
下面给出递推法求第 n {n} n 个卡特兰数的程序,一样可以求解本题。
#include
using namespace std;
typedef unsigned long long ull;
int main()
{
int n = 4;
ull catalan[34] = {0, 1};
for(int i = 2; i < 34; i++)
catalan[i] = catalan[i - 1] * (4 * i - 2) / (i + 1);
printf("%llu", catalan[n]);
return 0;
}
答案:14
见 周六下午 C。
问题描述
给定三个整数 a, b, c,如果一个整数既不是 a 的整数倍也不是 b 的整数倍还不是 c 的整数倍,则这个数称为反倍数。
请问在 1 至 n 中有多少个反倍数。
输入格式
输入的第一行包含一个整数 n。
第二行包含三个整数 a, b, c,相邻的两个数之间用一个空格分隔。
输出格式
输出一个整数,表示答案。
样例输入
30
2 3 6
样例输出
10
评测用例规模与约定
对于 40% 的评测用例,1 <= n <= 10000。
对于 80% 的评测用例,1 <= n <= 100000。
对于所有评测用例,1 <= n <= 1000000, 1 <= a <= n,1 <= b <= n,1 <= c <= n。
题解
枚举 1 到 n 中的每个整数,逐一判断其是否是反倍数即可。
#include
using namespace std;
int a, b, c;
int check(int i)
{
if(i % a == 0 || i % b == 0 || i % c == 0)
return 0;
return 1;
}
int main()
{
int n;
int ans = 0;
cin>>n>>a>>b>>c;
for(int i = 1; i <= n; i++)
if(check(i))
ans ++;
printf("%d", ans);
return 0;
}
问题描述
给定正整数 n,请问在整数 1 至 n 中, 数字中没有数位相同的数有多少个?
例如,当 n = 30时,除开 11 和 22 以外,其他的数都没有数位相同,因此答案为28。
输入格式
输入的第一行包含一个整数 n。
输出格式
输出一个整数,表示答案。
样例输入
30
样例输出
28
评测用例规模与约定
对于 40% 的评测用例,1 <= n <= 1000。
对于 80% 的评测用例,1 <= n <= 100000。
对于所有评测用例,1 <= n <= 1000000。
题解
枚举 1 到 n 中的每个整数,逐一判断其是否有数位相同即可。
#include
using namespace std;
int v[10];
int check(int i)
{
memset(v, 0, sizeof(v));
while(i)
{
if(v[i % 10])
return 0;
v[i % 10] = 1;
i /= 10;
}
return 1;
}
int main()
{
int n;
cin>>n;
int ans = 0;
for(int i = 1; i <= n; i++)
if(check(i))
ans++;
printf("%d", ans);
return 0;
}
见周六上午 E。
问题描述
一个平面向量表示从一个坐标点到另一个坐标点的变化量,一般用两个数(x, y)来表示。
两个向量相加是指分别将对应的两个数相加,例如(x_1, y_1)与(x_2, y_2)相加后得(x_1 + x_2, y_1 + y_2)。
如果两个向量相加后,得到的向量两个值相等,我们称这写两个向量为和谐向量对。例如(3, 5)和(4, 2)是和谐向量对。
给定 n 个向量,问能找到多少个和谐向量对?
输入格式
输入的第一行包含一个整数 n,表示向量的个数。
接下来的 n 行,每行两个整数 x_i, y_i, 表示一个向量。
输出格式
输出一个整数,表示有多少个和谐向量对。
请注意,自己和自己不能成为一个和谐向量对。
样例输入
5
9 10
1 3
5 5
5 4
8 6
样例输出
2
样例输入
4
1 1
2 2
1 1
2 2
样例输出
6
评测用例规模与约定
对于 70% 的评测用例,1 <= n <= 1000。
对于所有评测用例,1 <= n <= 100000, -1000000 <= x_i, y_i <= 1000000。
题解
对于第 i {i} i 个向量,遍历从 i + 1 {i+1} i+1 到 i m a x {{\mathop{{i}}\nolimits_{{max}}}} imax 的向量并统计有多少个能与其构成和谐向量对,统计答案即可。时间复杂度 O ( n 2 ) {{ {\rm O} \left( \mathop{{n}}\nolimits^{{2}} \right) }} O(n2),可以通过70%的评测用例。
分析
对于每个向量,令 t = x − y {t = x - y} t=x−y 。分析可得:若 t i = − t j {{{\mathop{{t}}\nolimits_{{i}}}=-\mathop{{t}}\nolimits_{{j}}}} ti=−tj ,则 ( i , j ) {(i,j)} (i,j) 为一个和谐向量对。
设 t = t i {t={\mathop{{t}}\nolimits_{{i}}}} t=ti 的向量有 a {a} a 个, t = t j {t={\mathop{{t}}\nolimits_{{j}}}} t=tj 的向量有 b {b} b 个,则这些向量共能组成 a ∗ b {a*b} a∗b 个和谐向量对。
求解
我们需要统计各个 t {t} t 值的向量数量,再使用乘法原理快速统计答案。
根据数据范围, − 2000000 ≤ t ≤ 2000000 {{-2000000 \le t \le 2000000}} −2000000≤t≤2000000。方便起见,我们令哈希函数为 h ( x ) = x + 2000000 {{h \left( x \left) =x+2000000\right. \right. }} h(x)=x+2000000,在int a[4000001]
对整个 t {t} t 值序列进行散列。由于函数为完美哈希函数,我们可以用表内数据统计出现次数,即以 a[h(x)]
统计 t = x {t=x} t=x 的向量对个数。
然后遍历正整数 x ( 1 ≤ x ≤ 2000000 ) {x(1 \le x \le 2000000)} x(1≤x≤2000000) ,将 a [ h ( x ) ] × a [ h ( − x ) ] {{a \left[ h \left( x \left) \left] \times a \left[ h \left( -x \left) \right] \right. \right. \right. \right. \right. \right. }} a[h(x)]×a[h(−x)] 统计入答案。特别的,对 a [ h ( 0 ) ] {a[h(0)]} a[h(0)] ,其代表 t {t} t 值为 0 {0} 0 的向量个数,对于答案的贡献是 a [ h ( 0 ) ] ∗ ( a [ h ( 0 ) ] − 1 ) / 2 {a[h(0)]*(a[h(0)]-1)/2} a[h(0)]∗(a[h(0)]−1)/2 。
时间复杂度为 Θ ( n + ( t m a x − t m i n ) ) {{ \Theta \left( n+ \left( \mathop{{t}}\nolimits_{{max}}-\mathop{{t}}\nolimits_{{min}} \left) \right) \right. \right. }} Θ(n+(tmax−tmin)) ,可以通过所有评测用例。
#include
using namespace std;
typedef long long ll;
const int maxn = 100005;
const int maxt = 4000007;
int t[maxn];
ll h[maxt];
int Hash(int n)
{
return n + 2000000;
}
void calHash(int n)
{
for(int i = 0; i < n; i++)
h[ Hash(t[i]) ]++;
}
int main()
{
int n;
cin>>n;
int tx, ty;
ll ans = 0;
for(int i = 0; i < n; i++)
{
scanf("%d %d", &tx, &ty);
t[i] = tx - ty;
}
calHash(n);
for(int i = 1; i <= 2000000; i++)
ans += h[Hash(i)] * h[Hash(-i)];
ans += h[Hash(0)] * (h[Hash(0)]-1) / 2;
printf("%lld", ans);
return 0;
}
U P D {UPD} UPD :对于此题,由于数据范围较小,我们选择使用编码容易且不易出错的直接散列法。若此题 t {t} t 的数据范围过大,可以选择 std::map
或 TreeMap(Java)
容器进行存储与计算。其内部实现是一颗红黑树(二叉平衡树的一种),可以在 Θ ( log 2 n ) {{ \Theta \left( {{\mathop{{ \text{log} }}\nolimits_{{2}}n}} \right) }} Θ(log2n) 时间内完成 find() / []
操作,使用树上遍历法可以在 O ( n log 2 n ) {{ {\rm O} \left( {n{\mathop{{ \text{log} }}\nolimits_{{2}}n}} \right) }} O(nlog2n) 时间完成答案统计。
问题描述
给定一个序列 a_1, a_2, …, a_n。其中 a_1 是最大的数,没有其他数与 a_1 相等。
对于从第二个数开始的每个数 a_i,请找到位置在 a_i 之前且比 a_i 大的,位置上距离 a_i 最近的数 a_j。称为 a_i 的前向距离。
对于给定的序列,请求出所有数的前向距离之和。
输入格式
输入的第一行包含一个整数 n,表示序列的长度。
第二行包含 n 个正整数,为给定的序列。
输出格式
输出一个整数,表示序列中所有数的前向距离之和。
样例输入
8
9 1 3 5 2 7 6 3
样例输出
14
评测用例规模与约定
对于 70% 的评测用例,1 <= n <= 1000。
对于所有评测用例,1 <= n <= 100000, a_i <= 1000000。
请注意答案可能很大,可能需要使用 long long 来保存。
题解
对于 i > 1 {i > 1} i>1 的每个 a i {{\mathop{{a}}\nolimits_{{i}}}} ai ,向前遍历从 i {i} i 到 1 {1} 1 的数直到第一个大于 a i {{\mathop{{a}}\nolimits_{{i}}}} ai 的数,计算其前向距离并统计答案即可。
时间复杂度 O ( n 2 ) {{ {\rm O} \left( \mathop{{n}}\nolimits^{{2}} \right) }} O(n2),可以通过70%的评测用例。
分析
由题意得,对于一个数 a i {{\mathop{{a}}\nolimits_{{i}}}} ai ,若在其后有 a j > a i {{\mathop{{a}}\nolimits_{{j}}>\mathop{{a}}\nolimits_{{i}}}} aj>ai ,则对于 a j {{\mathop{{a}}\nolimits_{{j}}}} aj 之后的数, a j {{\mathop{{a}}\nolimits_{{j}}}} aj 必能替代其成为答案。
求解
我们可以考虑使用一个单调递减的栈。栈中元素是一个结构体,含有
属性。
先将 a 1 {{\mathop{{a}}\nolimits_{{1}}}} a1 对应结构体入栈,对于随后的元素
由于栈具有有序性质,我们可以在操作
不断弹出直到其小于栈顶元素
时使用二分法(折半法),用 O ( log 2 n ) {{O \left( {{\mathop{{ \text{log} }}\nolimits_{{2}}n}} \right) }} O(log2n) 时间定位到其前向元素。
则单调栈+二分的算法使得不管如何构造的数据都有 O ( n log 2 n ) {{O \left( {{n\mathop{{ \text{log} }}\nolimits_{{2}}n}} \right) }} O(nlog2n) 的复杂度上界,可以通过所有评测用例。
#include
using namespace std;
typedef long long ll;
const int maxn = 100005;
typedef struct
{
int index;
int num;
} SNode;
stack<SNode> s;
int a[maxn];
int main(void)
{
int n;
cin>>n;
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
SNode temp;
temp.index = 1, temp.num = a[1];
s.push(temp);
long long ans = 0;
for(int i = 2; i <= n; i++)
{
while(s.top().num <= a[i])
{
s.pop();
}
ans += i - s.top().index;
temp.index = i, temp.num = a[i];
s.push(temp);
}
printf("%lld", ans);
}
见 周六下午 B。
见 周六下午 A。
问题描述
一棵含有2019个结点的数,最多包含多少个叶结点?
题解
让2018个叶节点的父节点都为根节点即可。
答案:2018
问题描述
一个包含有2019个节点的二叉树,最少有多少层?
注意当一棵二叉树只有一个节点时为一层。
题解
当一棵二叉树为完全二叉树(如果能的话,为满二叉树)时,其层数最少,为 ⌊ log 2 n ⌋ + 1 {{{ \left\lfloor {{\mathop{{ \text{log} }}\nolimits_{{2}}n}} \right\rfloor }+1}} ⌊log2n⌋+1。
若要编写程序计算该值,需先了解换底公式
log a x = log b x log b a {\mathop{{ \text{log} }}\nolimits_{{a}}x=\frac{{\mathop{{ \text{log} }}\nolimits_{{b}}x}}{{\mathop{{ \text{log} }}\nolimits_{{b}}a}}} logax=logbalogbx
C语言的库函数 log()
是求自然对数的函数,若想求 log a x {{\mathop{{ \text{log} }}\nolimits_{{a}}x}} logax ,可使用换底公式求 log e x log e a {{\frac{{\mathop{{ \text{log} }}\nolimits_{{e}}x}}{{\mathop{{ \text{log} }}\nolimits_{{e}}a}}}} logealogex ,即log(x)/log(a)
。
#include
using namespace std;
int main(void)
{
int a = 2019;
int ans = floor( log(2019) / log(2) ) + 1;
printf("%d", ans);
return 0;
}
见 周六上午 G。
见 周六上午 F。
问题描述
给定整数 m,在数列 a_1, a_2, …, a_n 中,如果两个数的和为 m 的倍数,则称为一个倍数对。
给定一个数列,请问数列中总共有多少个倍数对。
输入格式
输入的第一行包含两个整数 n,m ,分别表示数列中元素个数和给定的整数 m。
第二行包含 n 个整数 a_1, a_2, …, a_n,相邻的整数间用空格分隔,表示给定的数列。
输出格式
输出一个整数,表示答案。
样例输入
6 3
6 1 2 5 6 2
样例输出
4
评测用例规模与约定
对于 70% 的评测用例,1 <= n <= 100。
对于所有评测用例,1 <= n <= 1000。
题解
对于第 i {i} i 个整数,遍历从 i + 1 {i+1} i+1 到 i m a x {{\mathop{{i}}\nolimits_{{max}}}} imax 的数字并统计有多少个能与其构成倍数对,统计答案即可。
#include
using namespace std;
const int maxn = 1004;
int a[maxn];
int m;
int check(int i)
{
if(i % m == 0)
return 1;
return 0;
}
int main(void)
{
int n;
int ans = 0;
cin>>n>>m;
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
for(int i = 0; i < n - 1; i++)
for(int j = i + 1; j < n; j++)
if(check(a[i] + a[j]))
ans++;
printf("%d", ans);
return 0;
}
见 周六下午 H。
见 周六上午H。
问题描述
2015年,全中国实现了户户通电。作为一名电力建设者,小明正在帮助一带一路上的国家通电。
这一次,小明要帮助 n 个村庄通电,其中 1 号村庄正好可以建立一个发电站,所发的电足够所有村庄使用。
现在,这 n 个村庄之间都没有电线相连,小明主要要做的是架设电线连接这些村庄,使得所有村庄都直接或间接的与发电站相通。
小明测量了所有村庄的位置(坐标)和高度,如果要连接两个村庄,小明需要花费两个村庄之间的坐标距离加上高度差的平方,形式化描述为坐标为 (x_1, y_1) 高度为 h_ 1 的村庄与坐标为 (x_2, y_2) 高度为 h_ 2 的村庄之间连接的费用为
( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( h 1 − h 2 ) 2 {{\sqrt{{\mathop{{{ \left( {\mathop{{{x}}}\nolimits_{{1}}-\mathop{{{x}}}\nolimits_{{2}}} \right) }}}\nolimits^{{2}}+\mathop{{{ \left( {\mathop{{{y}}}\nolimits_{{1}}-\mathop{{{y}}}\nolimits_{{2}}} \right) }}}\nolimits^{{2}}}}+}\mathop{{{{ \left( {\mathop{{{h}}}\nolimits_{{1}}-\mathop{{{h}}}\nolimits_{{2}}} \right) }}}}\nolimits^{{2}}} (x1−x2)2+(y1−y2)2+(h1−h2)2
由于经费有限,请帮助小明计算他至少要花费多少费用才能使这n个村庄都通电。
题解
一道很明显的最小生成树题目。直接根据公式赋权值,然后求其最小生成树即可。
使用 Kruskal 算法,同时利用数据结构 二叉堆 进行优化,时间复杂度为 O ( n 2 log 2 n 2 ) {{O \left( {\mathop{{n}}\nolimits^{{2}}{\mathop{{ \text{log} }}\nolimits_{{2}}\mathop{{n}}\nolimits^{{2}}}} \right) }} O(n2log2n2) 。
考虑到该题为完全图,可以使用 Prim 算法,时间复杂度为 O ( n 2 ) {{O \left( {\mathop{{n}}\nolimits^{{2}}} \right) }} O(n2)。
#include
using namespace std;
const int maxn = 1004;
const double MAX = 1e9;
int n;
double a[maxn][maxn], d[maxn], ans;
bool v[maxn];
typedef struct
{
int x;
int y;
int h;
} point;
point p[maxn];
void init()
{
for(int i = 0; i <= n; i++)
{
for(int j = 0; j <= n; j++)
a[i][j] = MAX;
d[i] = MAX;
}
}
void Prim()
{
memset(v, 0, sizeof(v));
d[1] = 0;
for(int i = 1; i < n; i++)
{
int x = 0;
for(int j = 1; j <= n; j++)
if(!v[j] && (x == 0 || d[j] < d[x])) x = j;
v[x] = 1;
for(int y = 1; y <= n; y++)
if(!v[y]) d[y] = min(d[y], a[x][y]);
}
}
int main(void)
{
cin>>n;
init();
for(int i = 1; i <= n; i++)
scanf("%d %d %d", &p[i].x, &p[i].y, &p[i].h);
for(int i = 1; i <= n - 1; i++)
for(int j = i + 1; j <= n; j++)
{
double temp = sqrt( (p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y-p[j].y) * (p[i].y-p[j].y)) + (p[i].h-p[j].h) * (p[i].h-p[j].h);
a[i][j] = a[j][i] = min(a[i][j], temp);
}
Prim();
for(int i = 2; i <= n; i++) ans += d[i];
printf("%f", ans);
return 0;
}
计算机基础知识 * 4
数论 * 4
基础数据结构 * 6
字符串 * 2
图 * 1
树 * 2
散列 * 1
朴素算法 * 11
贪心算法 * 1
深度优先遍历 * 2
广度优先遍历 * 2
动态规划 * 3
高级数据结构 * 2
RMQ * 1
单调栈 * 1
图论 * 1