【机试C++】题目合集-1
题单
模拟、枚举 🥲
- 洛谷-题单:【算法1-1】模拟与高精度
- AcWing 792. 高精度减法
- P1008 [NOIP 1998 普及组] 三连击【暴力】
- P2010 [NOIP 2016 普及组] 回文日期【闰年,日期,回文数】
- 洛谷-题单:【入门4】数组
- 洛谷-题单:【算法1-3】暴力枚举【模拟枚举】【搜索枚举】,目前只做了前者
- P1149 [NOIP 2008 提高组] 火柴棒等式【枚举】【暴力,打表】
数学
前缀和
- 求和 2080【一维前缀和】【数学】【int溢出】
- 可获得的最小取值 3142【一维前缀和】【数学】【贪心】
- 异或和之和 3507【位运算】【超时邪门】【一维前缀和(不会)】
- P2004 领地选择【二维前缀和】
- B3612 【深进1.例1】求区间和【一维前缀和】
- P8218 【深进1.例1】求区间和,同上
- P5638 【CSGRound2】光骓者的荣耀【一维前缀和】【模拟】数据量大,分别开一个原数组和一个前缀和数组会爆内存
- P6625 [省选联考 2020 B 卷] 卡牌游戏【一维前缀和】【模拟】【贪心】
- P10233 [yLCPC2024] A. dx 分计算【一维】
差分
- 重新排序 2128【前缀和】【差分】,好题,有意思的思路
- P2367 语文成绩【一维差分】
- 推箱子【一维差分】,遍历求出t行之间缺口个数,求出最大缺口数,则清除障碍最少
- P3397 地毯【二维差分】
排序
- P1177 【模板】排序
- P1908 逆序对 【归并排序】【数学】
- 洛谷-题单:【算法1-2】排序
双指针
- P1638 逛画展【变长滑动窗口】
- P1102 A-B 数对【变长滑动窗口】
二分
- P2249 【深基13.例1】查找【整数二分】
- P1024 [NOIP 2001 提高组] 一元三次方程求解【小数二分】
- P1824 [USACO05FEB] 进击的奶牛【最小值最大化】
- P1182 数列分段 Section II【最大值最小化】【贪心】
- P1678 烦恼的高考志愿【二分 / lower_bound】【模拟】【排序】
- 洛谷-题单:【算法1-6】二分查找与二分答案
4.高频知识点:数学,排序,GCD ——等差数列:https://www.lanqiao.cn/problems/192/learning/
5.高频知识点:日期枚举 ——回文日期:https://www.lanqiao.cn/problems/498/learning/
6.高频知识点:枚举,计算贡献 ——子串分值和:https://www.lanqiao.cn/problems/1037/learning/
9.高频知识点:数位拆分——卡片:https://www.lanqiao.cn/problems/2383/learning/
11.高频知识点:前缀和,二分,数学——123:https://www.lanqiao.cn/problems/1591/learning/
12.高频知识点:贪心,思维——巧克力:https://www.lanqiao.cn/problems/1596/learning/
模拟
[NOIP 2016 提高组] 玩具谜题
- s[i][0] == 0,朝内;s[i][0] == 1,朝外
- a == 0,向左,减法;a == 1,向右,加法。
- 对总人数取余。
我的代码
#include<iostream>
using namespace std;
string s[100005][2];// 人从1开始计数,1 → n
int main(){
int n, m, a, si;// n个人,m个指令,a表示向左or向右,si表示旁边第几个人
cin >> n >> m;
for(int i = 1;i <= n;i++) cin >> s[i][0] >> s[i][1];
// 所有指令从第一个人开始找
int idx = 1; // 当前是第几个人
while(m-- > 0){
cin >> a >> si;
if(a == 0){// 向左
if(s[idx][0] == "0") idx = (idx - si + n) % n;// 朝内
else idx = (idx + si) % n;// 朝外
}else{// 向右
if(s[idx][0] == "0") idx = (idx + si) % n;// 朝内
else idx = (idx - si + n) % n;// 朝外
}
if(idx == 0) idx = n; // 如果a == idx,相减结果是0,这个需要特殊处理
}
cout << s[idx][1];
return 0;
}
大佬的代码:
ans = (ans+(a^out[ans]?s:-s+n))%n;:异或a^out[ans]用于判断实际移动方向是否反转,将朝向 + 指令方向统一成顺/逆时针,从而把4种情况压缩成1行代码。
等价于
a ^ out == 0 → 顺时针(ans += -s + n;)
a ^ out == 1 → 逆时针(ans += s;)
ans = (ans == 0? n : ans);:由于编号是 1 ~ n,但% n会变成 0 ~ n-1,所以如果结果是 0 → 实际是第 n 个,需要做一个更正的处理。- 题目中规定:以逆时针为顺序给出每个玩具小人的朝向和职业
点击查看代码
#include <cstdio>
#include <cstdlib>
using namespace std;
#define MAXN 100100
int n,m,ans=1,s,a,out[MAXN];//out代表小人儿的朝向,ans是“凶手”的编号
char name[MAXN][75];//职业名称
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%s",&out[i],name[i]);
for(int i=1;i<=m;i++){
scanf("%d%d",&a,&s);//下一行用到了异或
ans = (ans+(a^out[ans]?s:-s+n))%n;
ans = (ans == 0? n : ans);
}
printf("%s\n",name[ans]);
return 0;
}
P4924 [1007] 魔法少女小Scarlet【矩阵旋转坐标变换】
对矩阵中的一部分矩阵(子矩阵),即一个奇数阶方阵按照顺时针或者逆时针旋转90°,该子矩阵的中心为旋转中心。

本例中代码使用f[][]表示新数组,g[][]表示原数组
原坐标:(i, j)
旋转中心:(a, b)
dx = i - a → i = dx + a
dy = j - b → j = dy + b
1. 顺时针 90°
(1)坐标变换:(dx, dy) → (dy, -dx)
(2)变成原坐标:
i' = a + dy = a + (j - b) = a - b + j
j' = b - dx = b - (i - a) = a + b - i
(3)总结变换代码:
f[a-b+j][a+b-i] = g[i][j];
2. 逆时针 90°
(1)坐标变换:(dx, dy) → (-dy, dx)
(2)变成原坐标:
i' = a - dy = a - (j - b) = a + b - j
j' = b + dx = b + (i - a) = b - a + i
(3)总结变换代码:
f[a+b-j][b-a+i] = g[i][j];
点击查看代码
#include<iostream>
using namespace std;
#define N 505
int a[N][N], b[N][N]; // a原数组,将目标数组旋转后的部分存储到b,然后放回到a
int n, m, x, y, r, z;
int main(){
cin >> n >> m;
int t = 1;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
a[i][j] = t++;
while(m-- > 0){
cin >> x >> y >> r >> z;
if(z == 0){ // 顺时针
for(int i = x - r;i <= x + r;i++)
for(int j = y - r;j <= y + r;j++)
b[x-y+j][x+y-i] = a[i][j];
for(int i = x - r;i <= x + r;i++)
for(int j = y - r;j <= y + r;j++)
a[i][j] = b[i][j];
}else{ // 逆时针
for(int i = x - r;i <= x + r;i++)
for(int j = y - r;j <= y + r;j++)
b[x+y-j][y-x+i] = a[i][j];
for(int i = x - r;i <= x + r;i++)
for(int j = y - r;j <= y + r;j++)
a[i][j] = b[i][j];
}
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= n;j++){
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}
[USACO2.4] 两只塔姆沃斯牛 The Tamworth Two【模拟】【生成专属值】
法一:下面这种方式是使用一个较大的时间值来判断是否能相遇,如果这个值很大且到达这个值还没有结束,那么就不能相遇。有赌的成分,但是整体10×10的规模不大。
点击查看代码
#include<iostream>
using namespace std;
char map[10][10];
string t;
int d[4][2] = {{-1, 0},{0, 1},{1, 0},{0, -1}};// 上右下左 (x,y) ,顺时针90°
int dnF = 0, dnC = 0;// F、C当前移动方向,向上
int sum = 0;// 总时间
int x1, y1, x2, y2; // 记录John和牛的位置
// 如果前方无障碍(地图边沿也是障碍),它们会按照原来的方向前进一步。否则它们会用这一分钟顺时针转 90 度。
void move_F(){
int xx = x1 + d[dnF][0];
int yy = y1 + d[dnF][1];
if(xx < 0 || xx > 9 || yy < 0 || yy > 9 || map[xx][yy] == '*') {
dnF = (dnF + 1) % 4;
}else{
x1 = xx;
y1 = yy;
}
}
void move_C(){
int xx = x2 + d[dnC][0];
int yy = y2 + d[dnC][1];
if(xx < 0 || xx > 9 || yy < 0 || yy > 9 || map[xx][yy] == '*') {
dnC = (dnC + 1) % 4;
}else{
x2 = xx;
y2 = yy;
}
}
int main(){
// for(int i = 0;i < 10;i++){
// cin >> t;
// strcpy(map[i], t.c_str());
// }
for(int i = 0;i < 10;i++){
cin >> t;
for(int j = 0;j < 10;j++){
map[i][j] = t[j];
if(t[j] == 'F') {
x1 = i; y1 = j;
}
if(t[j] == 'C'){
x2 = i; y2 = j;
}
}
}
while(true){
sum++;
move_C();
move_F();
if(x1 == x2 && y1 == y2){ // 抓到了
cout << sum;
break;
}
if(sum > 100000){ // 长时间没抓到就是抓不到了
cout << 0;
break;
}
}
return 0;
}
法二:如果两个物体先后两次从同一个方向走到同一个地点,就可以说它们陷入了死循环,但如何判断是否是死循环??
答:可以通过生成专属值的方法来判断:农夫的x坐标 + 他的y坐标 * 10 + 奶牛的x坐标 * 100 + 奶牛的y坐标 * 1000 + 农夫的方向 * 10000 + 奶牛的方向 * 40000,其中农夫方向最多为4
只有二者的坐标、方向完全跟之前的某次相同才可以说是循环,所以给这6个值分别设置6个不同的权重,得到的结果相当于拼接数字。
- 由于每个变量的取值范围是有限的,这样可以防止相互影响。
- 其本质:把多维状态 → 映射成一维唯一编号
各个维度的取值范围(可以看到各个维度之间范围是没有重叠部分的)
坐标范围:0~9
方向范围:0~3
fx:0~9
fy:9,10~90(坐标为0时,fy=9,其他维度同理)
cx:90,100~900
cy:900,1000~9000
农夫方向:9000,10000~30000
奶牛方向:40000~...
实现代码
bool zt[160005];
tdz=f[1]+f[2]*10+c[1]*100+c[2]*1000+f[0]*10000+c[0]*40000;
if (zt[tdz]){
cout<<0<<endl;
return 0;
}
P2241 统计方形(数据加强版)【数学】【枚举】
相当于求在n × m的矩阵上,任意两个点有多少种可能
- 固定左上点(x1,y1)时,求右下点的位置有多少种,排除二者在同一条直线上(横向、纵向)
- 要求(x1,y1)必须在(x2,y2)的左上方,防止重复
数据比较大,当输入
n = 400, m = 400时,超出int最大值

点击查看代码
#include<iostream>
#include<algorithm> // 算法
using namespace std;
#define ll long long
ll ans1, ans2, sum;
int n, m;
int main(){
cin >> n >> m;
n += 1, m += 1;
sum = 0, ans1 = 0;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
sum += (n - i)*(m - j);
ans1 += min(n - i, m - j);
}
}
ans2 = sum - ans1;
cout << ans1 << " " << ans2;
return 0;
}
P3799 小 Y 拼木棒【枚举】【组合数学】
由于只取4根木棍,则一定有两根一样长,第三根由另外两根组成。
- 记录每个长度的木棍有几根,枚举所有不少于两根长度相同的木棍长度
x,然后遍历i = [1, x/2],看有没有长度为j = x - i的,有的话作组合问题求有几种就可以。 - 另外如果
j == i,即第三根木根分成相等的两段,需要额外判断长度为j的木棍个数书否num[j] >= 2。
点击查看代码
#include<iostream>
using namespace std;
const int MOD = 1e9 + 7;
int a, num[5005], n, MIN = 5000, MAX = -1;// 木棍长度a,长度为i的有num_i根,一共n根
long long ans = 0;
int main(){
cin >> n;
for(int i = 0;i < n;i++){
cin >> a;
num[a]++;
MIN = min(MIN, a);
MAX = max(MAX, a);
}
// 不可能用四根长度为MIN的组成正三角形,最多也就三根,所以从MIN+1开始遍历
for(int i = MIN + 1;i <= MAX;i++){ // 遍历根数大于2的
if(num[i] < 2) continue;
for(int j = MIN;j <= i / 2;j++){ // 二者之和为i的所有可能情况
if(j != i - j) //两根不一样长
ans += num[i] * (num[i] - 1) / 2 * num[j] * num[i - j] % MOD;
else if(j * 2 == i && num[j] >= 2) // 两根一样长
ans += num[i] * (num[i] - 1) / 2 * num[j] * (num[j] - 1) / 2 % MOD;
ans %= MOD; //即使取模了,相加之后仍然可能超出范围
}
}
cout << ans;
return 0;
}
前缀和 & 差分
重新排序 2128【一维前缀和】【一维差分】
- a原数据,s是a的前缀和,fq是a的每个元素在m个子区间中一共出现的次数(差分数组)
- 记录完原始数据后,对a升序排序,根据新数组及其出现的次数求出查询结果总和
- 由于要求最后查询结果之和越大越到,所以fq也做排序,保证越大的数查到的次数越多,这样结果就越大
点击查看代码
#include <iostream>
#include<algorithm>
using namespace std;
int m, n, l, r;
long long a[100005], s[100005], fq[100005], sum1 = 0, sum2 = 0;
int main()
{
cin >> n;
for(int i = 1;i <= n;i++){
cin >>a[i];
s[i] = s[i - 1] + a[i];
}
cin >> m;
while(m-- > 0){
cin >> l >> r;
r = r > n ? n : r;
sum1 += s[r] - s[l - 1];
fq[l]++;
fq[r + 1]--;
}
for(int i = 1;i <= n;i++) fq[i] += fq[i - 1];// 差分 → 前缀和,对每个元素的次数
sort(a + 1, a + 1 + n);
sort(fq + 1, fq + 1 + n);
for(int i = 1;i <= n;i++) sum2 += fq[i] * a[i];
cout << sum2 - sum1;
return 0;
}
异或和之和 3507【位运算】【一维前缀和】不会优化超时
大佬代码:
点击查看代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], cnt[25][5], n; //cnt[i][j]: 第i位j的个数
long long ans;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
//异或前缀和
a[i] ^= a[i - 1];
}
//[i, j]的异或和 = a[j] ^ a[i - 1],因为 a ^ b = c,a ^ c = b
// for(int i = 1; i <= n; i++)
// for(int j = i; j <= n; j++)
// ans += a[j] ^ a[i - 1];
//下面就是优化这个步骤
//遍历二进位每一位
for(int i = 0; i <= 20; i++)
//遍历每一个数,j=0就是左右区间相等的情况
for(int j = 0; j <= n; j++)
cnt[i][(a[j] >> i) & 1]++;
//乘法原理,把所有情况乘起来
for(int i = 0; i <= 20; i++){
ans += (long long)cnt[i][0] * cnt[i][1] * (1 << i);
}
printf("%lld", ans);
}
推箱子
采用一维差分,步骤:
- 由于输入的数据是每列缺口的数据,可以每列按行对应累加得到一维差分数组
- 求前缀和,得到每行的缺口数量
- 再求前缀和,得到每行缺口数量的前缀和
- 遍历高度为H的区间,即
[i, i + H - 1],求该区间中缺口数并取缺口数最大的值,这样需要清除的障碍最少
点击查看代码
#include<iostream>
using namespace std;
int N, H, l, h;
long long sum, max = -1, a[1000005];
int main()
{
cin >> N >> H;
for(int i = 1;i <= N;i++) {
cin >> l >> h;
a[l]++;
a[h + 1]--; //差分数组
}
for(int i = 1;i <= N;i++) a[i] += a[i - 1]; //每行的缺口数
for(int i = 1;i <= N;i++) a[i] += a[i - 1]; // 每行缺口数的前缀和
for(int i = 1;i + H - 1 <= N;i++){
long long t = a[i + H - 1] - a[i - 1];
if(t > sum) sum = t;
}
long long tmp = N * H;
cout << tmp - sum;
return 0;
}
双指针
P1638 逛画展【双指针】【变长滑动窗口】
点击查看代码
#include<iostream>
using namespace std;
int n, m, a[1000005], b[2005], min_num = 1000005, num, ansl, ansr, l, r;
//min_num 最少要买几幅画, num 当前区间有几个画家, ansl 最优区间的左端点, ansr 最优区间的右端点
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i++) cin >> a[i];
l = 1, r = 1, num = 1, b[a[1]] = 1; // 将第一个画放到区间中,画家数+1
while(l <= r && r <= n){
// 对于已经到达的区间,分情况讨论
if(num < m){ // 当前区间没有看到所有画家作品
r++; // 右移扩大区间
if(b[a[r]] == 0) num++; // 如果没有该画家作品,画家数+1
b[a[r]]++; // 该画家的作品数+1
}else{
if(min_num > r - l + 1) { // 此时真正计算到num中的区间其实是[l,r]
min_num = r - l + 1;//记录最小区间数
ansl = l, ansr = r;
}
// 在以i为左端点的最小区间找到之后,i++,此时找以i+1为左端点的最小区间
b[a[l]]--;
if(b[a[l]] == 0) num--;
l++;
}
}
cout << ansl << " " << ansr;
return 0;
}
P1102 A-B 数对【双指针】【变长滑动窗口】【模拟】
点击查看代码
#include<iostream>
#include<algorithm>
using namespace std;
long long a[200005], ans = 0;
int N, C;
int main(){
cin >> N >> C;
for(int i = 1;i <= N;i++) cin >> a[i];
sort(a + 1, a + 1 + N);// 升序
int begin = 1, end = 1;
for(int i = 1;i <= N;i++){// 遍历减数a[i]
// 遍历找扫被减数的范围[begin, end - 1]
// begin是第一个a[begin] - a[i] = C
while(a[begin] - a[i] < C && begin < N) begin++; // begin最大是N
// end是第一个a[end] - a[i] > C,即end - 1是最后一个a[end - 1] - a[i] = C
while(a[end] - a[i] <= C && end <= N) end++; // 由于被减数的范围取到end-1,所以end需要取到N+1
if(begin <= end - 1 && a[end - 1] - a[i] == C && a[begin] - a[i] == C){
ans += end - 1 - begin + 1;
}
}
cout << ans;
return 0;
}
排序
P1908 逆序对【归并排序】
偷的大佬的例子:
在某个时候,
左区间: 5 6 7 下标为i
右区间: 1 2 9 下标为j

合并过程如下
- step 1:由于 5>1,所以产生了逆序对,这里,我们发现,左区间所有还没有被合并的数都比 1 大,所以1与左区间所有元素共产生了 3 个逆序对,统计答案并合并
- step 2:由于 5>2,由上产生了3对逆序对,统计答案并合并 2
- step 3:由于 5<9, 没有逆序对产生,右区间下标 j++
- step 4:由于 6<9, 没有逆序对产生,右区间下标 j++
- step 5:由于 7<9, 没有逆序对产生,右区间下标 j++
- step 6:由于右区间已经结束,正常执行合并左区间剩余,结束
点击查看代码
// 归并排序,答案可能很大,开long long
#include<iostream>
using namespace std;
int n, a[500005], t[500005];
long long ans = 0;
void merge_sort(int l, int r){
if(l >= r) return;
int mid = l + r >> 1;
merge_sort(l, mid);
merge_sort(mid + 1, r);
int i = l, j = mid + 1;// 左右两个区间的左端点,用于遍历
int k = l; // t[]作为临时数组,用于记录两个区间合并后的数组,k是合并后区间的索引
while(i <= mid && j <= r){ // 两个区间都没有遍历完
if(a[i] > a[j]){
ans += mid - i + 1;
t[k++] = a[j++];
}else{
t[k++] = a[i++];
}
}
while(i <= mid) t[k++] = a[i++];
while(j <= r) t[k++] = a[j++];
for(int p = l;p <= r;p++) a[p] = t[p];
}
int main(){
cin >> n;
for(int i = 0;i < n;i++) cin >> a[i];
// 在合并是通过两个区间的比较计算逆序对的数量
merge_sort(0, n - 1);
cout << ans;
return 0;
}
二分
P1024 [NOIP 2001 提高组] 一元三次方程求解【小数二分 / 实数二分】
点击查看代码
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<cmath>
using namespace std;
double a, b, c, d;
double f(double x){
return a * pow(x, 3) + b * pow(x, 2) + c * x + d;
}
int main(){
cin >> a >> b >> c >> d;
// 两个根的差的绝对值>=1,保证了每一个大小为1的区间里至多有1个解
// 遍历每个长度为1的区间,对每个区间都做一次二分
for(double l = -100.0;l <= 100.0;l++){
double r = l + 1;
if(f(l) == 0) cout << fixed << setprecision(2) << l << " ";
// 根刚好落在端点上时,f(l) * f(r) = 0,不会进入以下区间,需要额外判断
else if(f(l) * f(r) < 0){
double eps = 1e-6;
double R = r, L = l;
while(R - L > eps){
double mid = (R + L) / 2;
if(f(L) * f(mid) < 0) R = mid;
else L = mid;
}
cout << fixed << setprecision(2) << L << " ";
}
}
return 0;
}
P1824 [USACO05FEB] 进击的奶牛【最小值最大化】
点击查看代码
// 最小值最大化
#include<iostream>
#include<algorithm>
using namespace std;
int n, m, ans = 1e9+5, a[100005];
bool check(int x){
int num = 1; // 可以分配的牛的数量
int l = a[1];// 记录两个相邻的牛中,左侧的牛所在位置
for(int i = 2;i <= n;i++){
if(a[i] - l >= x) {
num++;
l = a[i];
}
}
return num >= m; // 可以放下m头牛,可增大距离试试,向右二分
}
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i++) cin >> a[i];
sort(a + 1, a + 1 + n);// 升序排序
int l = 1, r = a[n] - a[1];
while(l < r){
int mid = (l + r + 1) / 2; //防止死循环
if(check(mid)) l = mid;
else r = mid - 1;
}
cout << l;
return 0;
}
P1182 数列分段 Section II【最大值最小化】【贪心】
注意:数据很大,求和的都需要开
long long。
点击查看代码
#include<iostream>
using namespace std;
#define ll long long
int a[100005], n, m;
ll sum = 0, MAX = 0;
bool check(ll x){
ll num = 0, sum = 0;
for(int i = 1;i <= n;i++){
if(sum + a[i] <= x) sum += a[i];
else {
num++;
sum = a[i];
}
if(num > m) return false;
}
num++; // 最后一个区间计数
return num <= m; // 满足题目,可以减小x以增加num,向左二分
}
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i++) {
cin >> a[i];
sum += a[i];
MAX = MAX > a[i] ? MAX : a[i];
}
ll l = MAX, r = sum;
while(l < r){
ll mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l;
return 0;
}

浙公网安备 33010602011771号