一级
商店折扣
题目描述
商店正在开展促销活动,给出了两种方案的折扣优惠。第一种方案是购物满 xxx 元减 yyy 元;第二种方案是直接打 nnn 折,也就是说价格变为原先的 n/10n/10n/10。这里的 x,y,nx, y, nx,y,n 均是正整数,并且 1≤y<x1 \leq y < x1≤y<x,1≤n<101 \leq n < 101≤n<10。
需要注意的是,第一种方案中满减优惠只能使用一次。例如购物满 101010 元减 333 元时,若挑选了价格总和为 333333 元的物品,只能减免 333 元,需要支付 303030 元。
小明在商店挑选了价格总和为 ppp 元的物品,结账时只能使用一种优惠方案。小明最少需要支付多少钱呢?
输入格式
四行,四个正整数 x,y,n,px, y, n, px,y,n,p,含义见题目描述。
输出格式
一行,一个小数,表示小明最少需要支付多少钱,保留两位小数。
输入输出样例 #1
输入 #1
8
7
9
10
输出 #1
3.00
输入输出样例 #2
输入 #2
8
7
2
11
输出 #2
2.20
说明/提示
对于所有测试点,保证 1≤y<x≤1001 \leq y < x \leq 1001≤y<x≤100,1≤n<101 \leq n < 101≤n<10,1≤p≤1001 \leq p \leq 1001≤p≤100。
题目解析
#include <iostream>
using namespace std;
int main()
{
int x,y,n;
cin >> x >> y >> n; // 读取满减门槛 x、减免金额 y、折扣 n
double p;
cin >> p; // 读取商品总价 p
double ans1 = p; // 存储第一种方案的支付金额,初始为原价
double ans2 = p; // 存储第二种方案的支付金额,初始为原价
if(ans1 >= x)ans1 -= y; // 若满足满减条件,应用第一种方案(满 x 减 y)
ans2 = ans2 * n / 10; // 应用第二种方案(打 n 折,即乘以 n / 10)
if(ans1 < ans2)printf("%.2lf",ans1); // 比较两种方案,输出较少的支付金额(保留两位小数)
else printf("%.2lf",ans2);
return 0;
}
金字塔
题目描述
金字塔由 nnn 层石块垒成。从塔底向上,每层依次需要 n×n,(n−1)×(n−1),⋯ ,2×2,1×1n \times n, (n-1) \times (n-1), \cdots, 2 \times 2, 1 \times 1n×n,(n−1)×(n−1),⋯,2×2,1×1 块石块。请问搭建金字塔总共需要多少块石块?
输入格式
一行,一个正整数 nnn,表示金字塔的层数。
输出格式
一行,一个正整数,表示搭建金字塔所需的石块数量。
输入输出样例 #1
输入 #1
2
输出 #1
5
输入输出样例 #2
输入 #2
5
输出 #2
55
说明/提示
对于所有测试点,保证 1≤n≤501 \leq n \leq 501≤n≤50。
题目解析
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n; // 读取输入的金字塔层数 n
int ans = 0; // 定义变量 ans 用于存储总石块数,初始化为 0
// 从第 n 层开始向下计算到第 1 层,累加每层的石块数
for(int i = n;i >= 1;i -- )ans += i * i;
cout << ans << endl; // 输出总石块数
return 0;
}
二级
优美的数字
题目描述
如果一个正整数在十进制下的所有数位都相同,小 A 就会觉得这个正整数很优美。例如,正整数 666 的数位都是 666,所以 666 是优美的。正整数 999999 的数位都是 999,所以 999999 是优美的。正整数 123123123 的数位不都相同,所以 123123123 并不优美。
小 A 想知道不超过 nnn 的正整数中有多少优美的数字。你能帮他数一数吗?
输入格式
一行,一个正整数 nnn。
输出格式
一行,一个正整数,表示不超过 nnn 的优美正整数的数量。
输入输出样例 #1
输入 #1
6
输出 #1
6
输入输出样例 #2
输入 #2
2025
输出 #2
28
说明/提示
对于所有测试点,保证 1≤n≤20251 \leq n \leq 20251≤n≤2025。
题目解析
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int ans = 0;
for(int i = 1;i <= n;i ++ )
{
int t = i;
int vis = t % 10;
int flag = 1;
while(t > 0)
{
int num = t % 10;
if(num != vis)
{
flag = 0;
break;
}
t /= 10;
}
if(flag)ans ++ ;
}
cout << ans << endl;
return 0;
}
菱形
题目描述
小 A 想绘制一个菱形。具体来说,需要绘制的菱形是一个 nnn 行 nnn 列的字符画,nnn 是一个大于 111 的奇数。菱形的四个顶点依次位于第 111 行、第 111 列、第 nnn 行、第 nnn 列的正中间,使用 # 绘制。相邻顶点之间也用 # 连接。其余位置都是 .。
例如,一个 555 行 555 列的菱形字符画是这样的:
..#..
.#.#.
#...#
.#.#.
..#..
给定 nnn,请你帮小 A 绘制对应的菱形。
输入格式
一行,一个正整数 nnn。
输出格式
输出共 nnn 行,表示对应的菱形。
输入输出样例 #1
输入 #1
3
输出 #1
.#.
#.#
.#.
输入输出样例 #2
输入 #2
9
输出 #2
....#....
...#.#...
..#...#..
.#.....#.
#.......#
.#.....#.
..#...#..
...#.#...
....#....
说明/提示
对于所有测试点,保证 3≤n≤293 \leq n \leq 293≤n≤29 并且 nnn 为奇数。
题目解析
解题思路:
老套的打印图形,有点意思,图形打印分为上下两半部分,即区间 [1,(n+1)/2][1,(n + 1) / 2][1,(n+1)/2] 和 [(n+1)/2,n][(n + 1) / 2,n][(n+1)/2,n]。对于每行的打印,可以进行分类讨论:
- num1num1num1 为首先输出的 . 的个数
- num2num2num2 为输出的 # 的个数
- midmidmid 为 # 之间需要填充的 . 的个数
- 若是其值大于 2,则需要使用 midmidmid 填充,num2num2num2 置为 2
- num3num3num3 为最后输出的 . 的个数
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
for(int i = 1;i <= n;i ++ )
{
if(i <= (n + 1) / 2)
{
int num1 = (n + 1) / 2 - i;
int num3 = num1;
int num2 = n - num1 - num3;
int mid = 0;
if(num2 > 2)
{
mid = num2 - 2;
num2 = 2;
}
for(int j = 1;j <= num1;j ++ )cout << ".";
if(num2 == 1)cout << "#";
else
{
cout << "#";
for(int j = 1;j <= mid;j ++ )cout << ".";
cout << "#";
}
for(int j = 1;j <= num3;j ++ )cout << ".";
cout << endl;
}
else
{
int num1 = i - (n + 1) / 2;
int num3 = num1;
int num2 = n - num1 - num3;
int mid = 0;
if(num2 > 2)
{
mid = num2 - 2;
num2 = 2;
}
for(int j = 1;j <= num1;j ++ )cout << ".";
if(num2 == 1)cout << "#";
else
{
cout << "#";
for(int j = 1;j <= mid;j ++ )cout << ".";
cout << "#";
}
for(int j = 1;j <= num3;j ++ )cout << ".";
cout << endl;
}
}
return 0;
}
三级
日历制作
题目描述
小 A 想制作 202520252025 年每个月的日历。他希望你能编写一个程序,按照格式输出给定月份的日历。
具体来说,第一行需要输出 MON TUE WED THU FRI SAT SUN,分别表示星期一到星期日。接下来若干行中依次输出这个月所包含的日期,日期的个位需要和对应星期几的缩写最后一个字母对齐。例如,202520252025 年 999 月 111 日是星期一,在输出九月的日历时,111 号的个位 111 就需要与星期一 MON 的最后一个字母 N 对齐。九月的日历输出效果如下:
MON TUE WED THU FRI SAT SUN
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
你能帮助小 A 完成日历的制作吗?
输入格式
一行,一个正整数 mmm,表示需要按照格式输出 202520252025 年 mmm 月的日历。
输出格式
输出包含若干行,表示 202520252025 年 mmm 月的日历。
输入输出样例 #1
输入 #1
9
输出 #1
MON TUE WED THU FRI SAT SUN
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
输入输出样例 #2
输入 #2
6
输出 #2
MON TUE WED THU FRI SAT SUN
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
说明/提示
对于所有测试点,保证 1≤m≤121 \leq m \leq 121≤m≤12。
题目解析
解题思路:
- 点开电脑的日历…就能看到每月的第一天是星期几
- a 数组存储每月的第一天是星期几
- b 数组存储每月有多少天,不用判断闰年,2025 是平年
- 输入日历
- 输出日历表头
- 对空的天数进行输出
- 根据 b 数组对应每月天数,结合 idx 进行换行、num 进行输出
#include <iostream>
using namespace std;
int main()
{
int m;
cin >> m;
int a[13] = {0,3,6,6,2,4,7,2,5,1,3,6,1};
int b[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
cout << "MON TUE WED THU FRI SAT SUN" << endl;
int num = 1;
for(int i = 1;i <= a[m] - 1;i ++ )printf(" ");
int idx = a[m];
while(num <= b[m])
{
if(idx > 7)
{
cout << endl;
idx = 1;
}
printf("%3d ",num);
num ++ ;
idx ++ ;
}
return 0;
}
数组清零
题目描述
小 A 有一个由 nnn 个非负整数构成的数组 a=[a1,a2,…,an]a = [a_1, a_2, \ldots, a_n]a=[a1,a2,…,an]。他会对阵组 aaa 重复进行以下操作,直到数组 aaa 只包含 0。在一次操作中,小 A 会依次完成以下三个步骤:
- 在数组 aaa 中找到最大的整数,记其下标为 kkk。如果有多个最大值,那么选择其中下标最大的。
- 从数组 aaa 所有不为零的整数中找到最小的整数 aja_jaj。
- 将第一步找出的 aka_kak 减去 aja_jaj。
例如,数组 a=[2,3,4]a = [2, 3, 4]a=[2,3,4] 需要 7 次操作变成 [0,0,0][0, 0, 0][0,0,0]:
[2,3,4]→[2,3,2]→[2,1,2]→[2,1,1]→[1,1,1]→[1,1,0]→[1,0,0]→[0,0,0] [2, 3, 4] \rightarrow [2, 3, 2] \rightarrow [2, 1, 2] \rightarrow [2, 1, 1] \rightarrow [1, 1, 1] \rightarrow [1, 1, 0] \rightarrow [1, 0, 0] \rightarrow [0, 0, 0] [2,3,4]→[2,3,2]→[2,1,2]→[2,1,1]→[1,1,1]→[1,1,0]→[1,0,0]→[0,0,0]
小 A 想知道,对于给定的数组 aaa,需要多少次操作才能使得 aaa 中的整数全部变成 0。可以证明,aaa 中整数必然可以在有限次操作后全部变成 0。你能帮他计算出答案吗?
输入格式
第一行,一个正整数 nnn,表示数组 aaa 的长度。
第二行,nnn 个非负整数 a1,a2,…,ana_1, a_2, \ldots, a_na1,a2,…,an,表示数组 aaa 中的整数。
输出格式
一行,一个正整数,表示 aaa 中整数全部变成 0 所需要的操作次数。
输入输出样例 #1
输入 #1
3
2 3 4
输出 #1
7
输入输出样例 #2
输入 #2
5
1 3 2 2 5
输出 #2
13
说明/提示
对于所有测试点,保证 1≤n≤1001 \leq n \leq 1001≤n≤100,0≤ai≤1000 \leq a_i \leq 1000≤ai≤100。
题目解析
#include <iostream>
using namespace std;
const int N = 2e6 + 10;
int a[N];
int main()
{
int n;
cin >> n;
for(int i = 1;i <= n;i ++ )cin >> a[i];
int cnt = 0;
while(1)
{
int flag = 1;
for(int i = 1;i <= n;i ++ )
{
if(a[i] != 0)
{
flag = 0;
break;
}
}
if(flag)break;
int maxidx = -1,minidx = -1;
int maxnum = -0x3f3f3f3f,minnum = 0x3f3f3f3f;
for(int i = 1;i <= n;i ++ )
{
if(a[i] >= maxnum)
{
maxnum = a[i];
maxidx = i;
}
if(a[i] < minnum && a[i] != 0)
{
minnum = a[i];
minidx = i;
}
}
a[maxidx] -= a[minidx];
cnt ++ ;
}
cout << cnt << endl;
return 0;
}
四级
排兵布阵
题目描述
作为将军,你自然需要合理地排兵布阵。地图可以视为 nnn 行 mmm 列的网格,适合排兵的网格以 1 标注,不适合排兵的网格以 0 标注。现在你需要在地图上选择一个矩形区域排兵,这个矩形区域内不能包含不适合排兵的网格。请问可选择的矩形区域最多能包含多少网格?
输入格式
第一行,两个正整数 n,mn, mn,m,分别表示地图网格的行数与列数。
接下来 nnn 行,每行 mmm 个整数 ai,1,ai,2,…,ai,ma_{i,1}, a_{i,2}, \ldots, a_{i,m}ai,1,ai,2,…,ai,m,表示各行中的网格是否适合排兵。
输出格式
一行,一个整数,表示适合排兵的矩形区域包含的最大网格数。
输入输出样例 #1
输入 #1
4 3
0 1 1
1 0 1
0 1 1
1 1 1
输出 #1
4
输入输出样例 #2
输入 #2
3 5
1 0 1 0 1
0 1 0 1 0
0 1 1 1 0
输出 #2
3
说明/提示
对于所有测试点,保证 1≤n,m≤121 \leq n, m \leq 121≤n,m≤12,0≤ai,j≤10 \leq a_{i,j} \leq 10≤ai,j≤1。
题目解析
#include <iostream>
using namespace std;
const int N = 5050;
int a[N][N];
int judge(int x,int y,int dx,int dy)
{
int cnt = 0;
for(int i = x;i <= x + dx;i ++ )
{
for(int j = y;j <= y + dy;j ++ )
{
if(a[i][j] == 0)return -1;
else cnt ++ ;
}
}
return cnt;
}
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i ++ )
{
for(int j = 1;j <= m;j ++ )
{
cin >> a[i][j];
}
}
int ans = 0;
for(int x = 1;x <= n;x ++ )
{
for(int y = 1;y <= m;y ++ )
{
for(int dx = 0;x + dx <= n;dx ++ )
{
for(int dy = 0;y + dy <= m;dy ++ )
{
ans = max(ans,judge(x,y,dx,dy));
}
}
}
}
cout << ans << endl;
return 0;
}
最长连续段
题目描述
对于 kkk 个整数构成的数组 [b1,b2,…,bk][b_1, b_2, \ldots, b_k][b1,b2,…,bk],如果对 1≤i<k1 \leq i < k1≤i<k 都有 bi+1=bi+1b_{i+1} = b_i + 1bi+1=bi+1,那么称数组 bbb 是一个连续段。
给定由 nnn 个整数构成的数组 [a1,a2,…,an][a_1, a_2, \ldots, a_n][a1,a2,…,an],你可以任意重排数组 aaa 中元素顺序。请问在重排顺序之后,aaa 所有是连续段的子数组中,最长的子数组长度是多少?
例如,对于数组 [1,0,2,4][1, 0, 2, 4][1,0,2,4],可以将其重排为 [4,0,1,2][4, 0, 1, 2][4,0,1,2],有以下 101010 个子数组:
[4],[0],[1],[2],[4,0],[0,1],[1,2],[4,0,1],[0,1,2],[4,0,1,2][4], [0], [1], [2], [4, 0], [0, 1], [1, 2], [4, 0, 1], [0, 1, 2], [4, 0, 1, 2][4],[0],[1],[2],[4,0],[0,1],[1,2],[4,0,1],[0,1,2],[4,0,1,2]
其中除 [4,0],[4,0,1],[4,0,1,2][4, 0], [4, 0, 1], [4, 0, 1, 2][4,0],[4,0,1],[4,0,1,2] 以外的子数组均是连续段,因此是连续段的子数组中,最长子数组长度为 3。
输入格式
第一行,一个正整数 nnn,表示数组长度。
第二行,nnn 个整数 a1,a2,…,ana_1, a_2, \ldots, a_na1,a2,…,an,表示数组中的整数。
输出格式
一行,一个整数,表示数组 aaa 重排顺序后,所有是连续段的子数组的最长长度。
输入输出样例 #1
输入 #1
4
1 0 2 4
输出 #1
3
输入输出样例 #2
输入 #2
9
9 9 8 2 4 4 3 5 3
输出 #2
4
说明/提示
对于 40%40\%40% 的测试点,保证 1≤n≤81 \leq n \leq 81≤n≤8。
对于所有测试点,保证 1≤n≤1051 \leq n \leq 10^51≤n≤105,−109≤ai≤109-10^9 \leq a_i \leq 10^9−109≤ai≤109。
题目解析
解题思路:
- 对数组排序然后去重
- 对遍历去重后的区间使用双指针即可
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2e6 + 10;
int a[N];
int main()
{
int n;
cin >> n;
for(int i = 1;i <= n;i ++ )cin >> a[i];
sort(a + 1,a + 1 + n);
int tail = unique(a + 1,a + 1 + n) - a - 1;
int ans = 0;
for(int i = 1;i <= tail;i ++ )
{
int j = i + 1;
while(j <= tail && a[j] - a[j - 1] == 1)j ++ ;
ans = max(ans, j - i);
i = j - 1;
}
cout << ans << endl;
return 0;
}
五级
有趣的数字和
题目背景
为保证只有时间复杂度合理的算法通过本题,本题时限下调。
题目描述
如果一个正整数的二进制表示包含奇数个 111,那么小 A 就会认为这个正整数是有趣的。
例如,777 的二进制表示为 (111)2(111)_2(111)2,包含 111 的个数为 333 个,所以 777 是有趣的。但是 9=(1001)29=(1001)_29=(1001)2 包含 222 个 111,所以 999 不是有趣的。
给定正整数 l,rl,rl,r,请你统计满足 l≤n≤rl\le n\le rl≤n≤r 的有趣的整数 nnn 之和。
输入格式
一行,两个正整数 l,rl,rl,r,表示给定的正整数。
输出格式
一行,一个正整数,表示 l,rl,rl,r 之间有趣的整数之和。
输入输出样例 #1
输入 #1
3 8
输出 #1
19
输入输出样例 #2
输入 #2
65 36248
输出 #2
328505490
说明/提示
【数据范围】
对于 40%40\%40% 的测试点,保证 1≤l≤r≤1041\le l\le r\le 10^41≤l≤r≤104。
对于另外 30%30\%30% 的测试点,保证 l=1l=1l=1 并且 r=2k−1r=2^k-1r=2k−1,其中 kkk 是大于 111 的正整数。
对于所有测试点,保证 1≤l≤r≤1091 \le l\le r\le 10^91≤l≤r≤109。
【提示】
由于本题的数据范围较大,整数类型请使用 long long。
题目解析
本题解在洛谷时限下调前后都可AC。
我们发现,对于连续的四个数字,其中必定有两个奇数,两个偶数,因此我们可以把整个数轴按照区间长度为 4 来进行讨论,通过打表能够更直观的看到该规律,打表代码如下:
#include <iostream>
#define int long long
using namespace std;
signed main()
{
int idx = 0;
// 部分打表
for(int i = 0;i <= 31;i ++ )
{
int t = i;
int cnt = 0;
while(t > 0)
{
if(t & 1)cnt ++ ;
t >>= 1;
}
idx ++ ;
if(cnt & 1)cout << i << " " << "符合条件" << endl;
else cout << i << endl;
if(idx == 4)
{
cout << endl;
idx = 0;
}
}
return 0;
}
打表结果:
0
1 符合条件
2 符合条件
3
4 符合条件
5
6
7 符合条件
8 符合条件
9
10
11 符合条件
12
13 符合条件
14 符合条件
15
16 符合条件
17
18
19 符合条件
20
21 符合条件
22 符合条件
23
24
25 符合条件
26 符合条件
27
28 符合条件
29
30
31 符合条件
对于每个长度为 4 的区间,我们可以对其离散化编号,将 lll 和 rrr 的坐标 / 4 即可得到区间编号,我们把区间编号从 0 开始,那么第 nnn 个区间对应的范围也就是 [n×4,n×4+3][n × 4,n × 4 + 3][n×4,n×4+3]。在这里我们把 num1num1num1 即为 lll 所处的区间编号,num2num2num2 即为 rrr 所处的区间编号,对于每个合理的区间,我们来进行分类讨论:
- 若是该区间完全被 [l,r][l,r][l,r] 覆盖,我们称之为全覆盖区间,合法区间范围为 [num1+1,num2−1][num1 + 1,num2 - 1][num1+1,num2−1],其有效计入答案值为两端数字之和 / 中间两个数字之和,由于两端数字之和 = 中间两个数字之和,**因此在这里,我们对全覆盖区间的有效答案求和就可以简单记为两端数字之和。**即:((i×4)+(i×4+3))((i × 4) + (i × 4 + 3))((i×4)+(i×4+3))。
- 对于没有被 [l,r][l,r][l,r] 全覆盖的区间,我们称之为半覆盖区间:
- 若是 lll 所在的半覆盖区间和 r 所在的半覆盖区间属于同一区间,那么我们暴力枚举区间 [l,r][l,r][l,r] 即可
- 若是 lll 和 rrr 所在区间不同,我们需要对区间 [l,num1×4+3][l,num1 × 4 + 3][l,num1×4+3] 和 [num2×4,r][num2 × 4,r][num2×4,r] 进行暴力枚举
- 最终答案即为全覆盖区间的有效答案 + 半覆盖区间的有效答案
由此,我们可以用 O(1)O(1)O(1) 时间复杂度来解决这个问题:
#include <iostream>
#define int long long
using namespace std;
signed main()
{
int l,r;
cin >> l >> r;
int num1 = l / 4;
int num2 = r / 4;
int ans = 0;
if(num1 == num2)
{
for(int i = l;i <= r;i ++ )
{
int t = i;
int cnt = 0;
while(t > 0)
{
if(t & 1)cnt ++ ;
t /= 2;
}
if(cnt & 1)ans += i;
}
}
else
{
for(int i = l;i <= num1 * 4 + 3;i ++ )
{
int t = i;
int cnt = 0;
while(t > 0)
{
if(t & 1)cnt ++ ;
t /= 2;
}
if(cnt & 1)ans += i;
}
for(int i = num2 * 4;i <= r;i ++ )
{
int t = i;
int cnt = 0;
while(t > 0)
{
if(t & 1)cnt ++ ;
t >>= 1;
}
if(cnt & 1)ans += i;
}
}
for(int i = num1 + 1;i <= num2 - 1;i ++ )
{
ans += ((i * 4) + (i * 4 + 3));
}
cout << ans << endl;
return 0;
}
数字选取
题目描述
给定正整数 nnn,现在有 1,2,…,n1,2,\ldots,n1,2,…,n 共计 nnn 个整数。你需要从这 nnn 个整数中选取一些整数,使得所选取的整数中任意两个不同的整数均互质(也就是说,这两个整数的最大公因数为 111)。请你最大化所选取整数的数量。
例如,当 n=9n=9n=9 时,可以选择 1,5,7,8,91,5,7,8,91,5,7,8,9 共计 555 个整数。可以验证不存在数量更多的选取整数的方案。
输入格式
一行,一个正整数 nnn,表示给定的正整数。
输出格式
一行,一个正整数,表示所选取整数的最大数量。
输入输出样例 #1
输入 #1
6
输出 #1
4
输入输出样例 #2
输入 #2
9
输出 #2
5
说明/提示
对于 40%40\%40% 的测试点,保证 1≤n≤10001\le n\le 10001≤n≤1000。
对于所有测试点,保证 1≤n≤1051\le n\le 10^51≤n≤105。
题目解析
解题思路:
- 1 肯定要作为答案加入
- 奇质数要作为答案加入
- 但凡 n≥2n \ge 2n≥2,证明一定存在 2 的幂次方,从 2 的幂次方中选取一个数作为答案加入
#include <iostream>
using namespace std;
const int N = 2e6 + 10;
// 埃氏筛法模板
int primes[N];
int st[N];
int idx = 0;
// 返回奇质数的个数
int get_primes(int n)
{
for(int i = 2;i <= n;i ++ )
{
if(!st[i])primes[idx ++ ] = i;
for(int j = i;j <= n;j += i)st[j] = true;
}
int cnt = 0;
for(int i = 0;i < idx;i ++ )
{
if(primes[i] & 1)cnt ++ ;
}
return cnt;
}
int main()
{
int n;
cin >> n;
int ans = 1 + get_primes(n);
// 若是 n >= 2,则一定存在 2 的幂次方
if(n >= 2)ans ++ ;
cout << ans << endl;
return 0;
}
六级
货物运输
题目描述
A 国有 nnn 座城市,依次以 1,2,…,n1,2,\ldots,n1,2,…,n 编号,其中 111 号城市为首都。这 nnn 座城市由 n−1n-1n−1 条双向道路连接,第 iii 条道路(1≤i<n1 \le i < n1≤i<n)连接编号为 ui,viu_i,v_iui,vi 的两座城市,道路长度为 lil_ili。任意两座城市间均可通过双向道路到达。
现在 A 国需要从首都向各个城市运送货物。具体来说,满载货物的车队会从首都开出,经过一座城市时将对应的货物送出,因此车队需要经过所有城市。A 国希望你设计一条路线,在从首都出发经过所有城市的前提下,最小化经过的道路长度总和。注意一座城市可以经过多次,车队最后可以不返回首都。
输入格式
第一行,一个正整数 nnn,表示 A 国的城市数量。
接下来 n−1n-1n−1 行,每行三个正整数 ui,vi,liu_i,v_i,l_iui,vi,li,表示一条双向道路连接编号为 ui,viu_i,v_iui,vi 的两座城市,道路长度为 lil_ili。
输出格式
一行,一个整数,表示你设计的路线所经过的道路长度总和。
输入输出样例 #1
输入 #1
4
1 2 6
1 3 1
3 4 5
输出 #1
18
输入输出样例 #2
输入 #2
7
1 2 1
2 3 1
3 4 1
7 6 1
6 5 1
5 1 1
输出 #2
9
说明/提示
对于 30%30\%30% 的测试点,保证 1≤n≤81 \le n \le 81≤n≤8。
对于另外 30%30\%30% 的测试点,保证仅与一条双向道路连接的城市恰有两座。
对于所有测试点,保证 1≤n≤1051 \le n \le 10^51≤n≤105,1≤ui,vi≤n1 \le u_i,v_i \le n1≤ui,vi≤n,1≤li≤1091 \le l_i \le 10^91≤li≤109。
树是一个无环图,对于树的遍历我们可以使用贪心,为了使答案最小化,有着如下策略:
- 对于根到叶子节点的最大路径,我们只需要遍历一次
- 对于其他路径,我们需要遍历两次
- sum 计算路径长度总和,maxl 记录从叶节点到根的最大路径长度
- 答案即为 sum * 2 - maxl
- 这道题目一定要开 long long
#include <iostream>
#include <cstring>
#define int long long
using namespace std;
const int N = 2e6 + 10;
// 邻接表建树
int e[N],ne[N],h[N],w[N],vis[N],idx;
// 初始化从叶节点到根节点的最长路径和树的路径长度总和
int maxl = -0x3f3f3f3f,sum = 0;
void init()
{
memset(h,-1,sizeof(h));
idx = 0;
}
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++ ;
}
// 搜索最长路径
void dfs(int u,int len)
{
for(int i = h[u];i != -1;i = ne[i])
{
int j = e[i];
if(!vis[j])
{
// 无回溯搜索
vis[j] = true;
dfs(j,len + w[i]);
}
}
maxl = max(maxl,len);
}
signed main()
{
init();
int n;
cin >> n;
for(int i = 1;i <= n - 1;i ++ )
{
int a,b,c;
cin >> a >> b >> c;
add(a,b,c);
add(b,a,c);
sum += c;
}
// 对根节点进行标记再进行搜索
vis[1] = true;
dfs(1,0);
cout << sum * 2 - maxl << endl;
return 0;
}
划分字符串
题目描述
小 A 有一个由 nnn 个小写字母组成的字符串 sss。他希望将 sss 划分为若干个子串,使得子串中每个字母至多出现一次。例如,对于字符串 street 来说,str + e + e + t 是满足条件的划分;而 s + tree + t 不是,因为子串 tree 中 e 出现了两次。
额外地,小 A 还给出了价值 a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,…,an,表示划分后长度为 iii 的子串价值为 aia_iai。小 A 希望最大化划分后得到的子串价值之和。你能帮他求出划分后子串价值之和的最大值吗?
输入格式
第一行,一个正整数 nnn,表示字符串的长度。
第二行,一个包含 nnn 个小写字母的字符串 sss。
第三行,nnn 个正整数 a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,…,an,表示不同长度的子串价值。
输出格式
一行,一个整数,表示划分后子串价值之和的最大值。
输入输出样例 #1
输入 #1
6
street
2 1 7 4 3 3
输出 #1
13
输入输出样例 #2
输入 #2
8
blossoms
1 1 2 3 5 8 13 21
输出 #2
8
说明/提示
对于 40%40\%40% 的测试点,保证 1≤n≤1031\le n\le 10^31≤n≤103。
对于所有测试点,保证 1≤n≤1051\le n\le 10^51≤n≤105,1≤ai≤1091\le a_i\le 10^91≤ai≤109。
题目解析
划分出的每个子串中,每个小写字母至多出现 1 次,要求子串价值之和最大。价值规则是 长度为 kkk 的子串,价值为 a[k]a[k]a[k],我们用 dp[i]dp[i]dp[i] 表示 “处理完字符串的前 iii 个字符时,能得到的最大价值之和”。
-
初始化 dp[i]dp [i]dp[i] 为 0
-
初始化 vis[26]vis[26]vis[26] 为 falsefalsefalse
-
遍历 kkk 从 1 到 26
-
先判断边界:若 i−k<0i - k < 0i−k<0,直接 break
-
找到子串的左端点索引:idx=i−kidx = i - kidx=i−k
-
检查字符 s[idx]s[idx]s[idx] 是否在 visvisvis 中:
- 若已存在:break
- 若不存在:标记 vis[s[idx]−′a′]=truevis[s[idx]-'a'] = truevis[s[idx]−′a′]=true,计算 dp[i−k]+a[k]dp[i - k] + a[k]dp[i−k]+a[k],用该值更新 dp[i]dp [i]dp[i]
-
-
遍历结束后,dp[i]dp [i]dp[i] 就是 前 iii 个字符的最大价值和
#include <iostream>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int a[N], dp[N];
string s;
signed main()
{
int n;
cin >> n;
cin >> s;
for(int i = 1;i <= n;i ++ )cin >> a[i];
dp[0] = 0;
// 计算前i个字符的最大价值和
for(int i = 1;i <= n;i ++ )
{
dp[i] = 0; // 初始化当前最大价值和为0
bool vis[26]; // 记录当前子串中出现的字符
for(int j = 0;j < 26;j ++ )vis[j] = false;
// 尝试以i为结尾的子串,长度k从1到26,子串为s[idx..i-1]
for(int k = 1;k <= 26;k ++ )
{
if(i - k < 0)break;
int idx = i - k;
char c = s[idx];
if(vis[c - 'a'])break;
vis[c - 'a'] = true;
dp[i] = max(dp[i], dp[i - k] + a[k]);
}
}
cout << dp[n] << endl;
return 0;
}
734

被折叠的 条评论
为什么被折叠?



