【机试C++】题目合集-1

题单

模拟、枚举 🥲

数学

前缀和

差分

排序

双指针

二分

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°,该子矩阵的中心为旋转中心。
image

本例中代码使用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最大值

image

点击查看代码
#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

image

合并过程如下

  • 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;
}
posted @ 2026-09-27 12:41  idle_life  阅读(27)  评论(0)    收藏  举报