算法---模拟/高精度/枚举

模拟

1. 模拟
模拟,顾名思义,就是题目让你做什么你就做什么,考察的是将思路转化成代码的代码能力。 这类题一般较为简单,属于竞赛里面的签到题(但是,万事无绝对,也有可能会出现让人非常难受的 模拟题),我们在学习语法阶段接触的题,大多数都属于模拟题。

题一 : 多项式输出

思路:

代码是逐项处理多项式的每一项,核心围绕"符号、数、次数"三个部分展开

  1. 遍历每一项(按次数从高到低
  • 通过 for (int i = n; i >= 0; i--) 遍历从最高次 n 到常数项 0 的所有项, i 就是当前项的次数, a 是当前项的系数。
  1. 处理"符号"(对应笔记的"符号"逻辑)
  • 先跳过系数为 0 的项( if (a == 0) continue );
  • 若系数为负:直接输出 - ;
  • 若系数为正:
  • 是最高次项( i == n ):不输出 + ;
  • 不是最高次项:输出 + 。
  1. 处理"数"(对应笔记的"数-先取绝对值"逻辑)

先把系数取绝对值( a = abs(a) ),再分情况:

  • 若系数≠1:直接输出系数;
  • 若系数=1:
  • 是常数项( i == 0 ):输出 1 ;
  • 不是常数项:不输出。
  1. 处理"次数"(对应笔记的"次数"逻辑)
  • 若次数=0(常数项):什么都不输出;
  • 若次数=1:输出 x ;
  • 若次数>1:输出 x^次数 。

代码:

cpp 复制代码
#include<iostream>
#include<cmath>
using namespace std;

int main()
{
	int n;
	cin >> n;
	for (int i = n;i >= 0;i--) //i == 5 4 3 2 1 0
	{
		int a;  
		cin >> a;
		//符号
		if (a == 0) continue;
		if (a < 0) 
			cout << "-";
		else
		{
			if (i!=n) cout << '+';
		}
		//数
		a = abs(a);
		if (a == 1)
		{
			if (i == 0) cout << a;
            else ;
		}
		else cout << a;
		//if (a != 1 || (a == 1 && i == 0))
		//	cout << a;
		//次数
		if (i == 0) continue;
		else if (i == 1) cout << 'x';
		else
			cout << "x^" << i;

	}


	return 0;
}

注意事项:

  1. 跳过系数为0的项:

题目要求"只包含系数不为0的项",所以必须用 if (a == 0) continue 跳过,否则会输出多余的项。

  1. 最高次项的符号:

最高次项系数为正时,开头不能有 + (比如样例1的 100x^5 开头没有 + ),代码中通过 if (i!=n) 控制了这一点。

  1. 系数为1的高次项:

比如 1x^3 要写成 x^3 ,代码中通过"系数=1且不是常数项时不输出"实现了这一点;但常数项 1 必须输出(比如样例2的末尾 +1 )。

  1. 次数的格式:

次数=1时输出 x (不是 x^1 ),次数>1时才输出 x^次数 ,别写混格式。


题目二 : 蛇形方阵

思路:

dx[] 和 dy[] :是方向向量数组,控制坐标的移动方向(对应"右、下、左、上"4个方向):

dx[i] :表示第 i 个方向下,行坐标(x)的变化量;

dy[i] :表示第 i 个方向下,列坐标(y)的变化量。

比如 dx[0]=0, dy[0]=1 对应"向右":行不变,列+1。

N :是矩阵的最大容量(设为15是因为题目中 n≤9 ,预留足够空间防止越界)。

arr[N][N] :是存储蛇形矩阵的二维数组,初始值为0,填充完成后保存最终的矩阵数据。

n :是输入的正整数,表示要生成的蛇形矩阵的边长( n×n 矩阵)。

ret :是当前要填充的数字,初始值为1(从1开始填),每填充一个位置就自增1,直到 ret > n×n (填满所有位置)。

x, y :是当前填充位置的坐标(行号 x 、列号 y ),初始值为 (1,1) (矩阵的左上角)。

pos :是当前的移动方向索引,对应 dx[]/dy[] 的下标:
pos=0 → 右; pos=1 → 下; pos=2 → 左; pos=3 → 上。

初始值为0(从"右"方向开始),遇到边界时通过 pos=(pos+1)%4 切换方向。

a, b :是下一个位置的坐标(临时变量),由当前坐标 (x,y) 加上方向向量 (dx[pos], dy[pos]) 计算得到,用于判断下一个位置是否越界/已填充。

代码:

cpp 复制代码
#include<iostream>
using namespace std;
//定义右下左上四个方向
int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };
const int N = 15;
int arr[N][N] = { 0 };


int main()
{
	int n; cin >> n;
	//模拟填数过程
	int ret = 1;   //当前位置要填的数
	int x = 1, y = 1;   //初始位置
	int pos = 0;   //当前方向

	while (ret <= n * n)
	{
		arr[x][y] = ret;
		//计算下一个位置
		int a = x + dx[pos];
		int b = y + dy[pos];
		//判断是否越界
		if (a <1 || a > n || b < 1 || b > n ||arr[a][b])
		{
			//更新出正确的该走的位置
			pos = (pos + 1) % 4;
			a = x + dx[pos], b = y + dy[pos];

		}
		x = a, y = b;
		ret++;

	}
	//输出
	for (int i = 1;i <= n;i++)
	{
		for (int j = 1;j <= n;j++)
		{
			printf("%3d", arr[i][j]);
		}
		printf("\n");
	}

	return 0;
}

注意点:

1. 四个判断条件( a < 1 || a > n || b < 1 || b > n || arr[a][b] )

四个条件的核心作用:判断"下一个位置 (a,b) 是否能走",只要满足任意一个,就说明当前方向走不通,必须切换方向。

  • a < 1 :下一个位置的行号小于1(越上边界);

  • a > n :下一个位置的行号大于 n (越下边界);

  • b < 1 :下一个位置的列号小于1(越左边界);

  • b > n :下一个位置的列号大于 n (越右边界);
    - arr[a][b] :下一个位置已经填过数字(因为初始为0,非0即已填充,可简化为 arr[a][b] ,等价于 arr[a][b] != 0 )。

  1. 顺序不影响结果:只要覆盖"越界"和"已填充"两种情况,四个边界条件的顺序可以随便换,但必须放在 arr[a][b] 前面(先判断是否在矩阵内,再判断是否已填充,逻辑更顺)。

2. pos 的取模( pos = (pos + 1) % 4 )

  1. 核心目的:实现"4个方向循环切换"(右→下→左→上→右...),避免方向索引超出 0-3 的范围。
  • 因为方向只有4个( pos 取值0/1/2/3),当 pos=3 (上方向)再切换时, (3+1)%4=0 ,刚好回到 pos=0 (右方向),形成循环。
  1. 必须在"走不通"时切换:只有当下一个位置不合法(触发四个判断条件)时,才执行取模切换方向,不能随便切换------否则会乱改方向,导致填数顺序错误。
    3. x、y 和 a、b 之间的关系

  2. x、y 是"当前位置", a、b 是"候选下一个位置":

  • x、y 存储的是正在填数的位置(比如刚填完 ret 的坐标);

  • a、b 是通过 x + dx[pos] 、 y + dy[pos] 计算出的"下一步想去的位置"(临时存储,用于判断)。

  1. 赋值逻辑:合法则替换,不合法则重算:
  • 若 a、b 合法(不越界、未填充):就把 a、b 赋值给 x、y ( x=a; y=b ),让 x、y 移动到下一个位置;

  • 若 a、b 不合法:不直接赋值,先切换 pos ,再重新计算 a、b (新方向的下一个位置),直到 a、b 合法,再赋值给 x、y 。

  1. 不能直接用 x、y 试错:如果不用 a、b ,直接修改 x、y 试方向,一旦不合法,还得把 x、y 回退( x -= dx[pos]; y -= dy[pos] ),容易出错且逻辑绕。

  2. 最后的换行( printf("\n") )

  3. 核心作用:让矩阵按"行"显示,否则所有数字会挤在一行,和样例格式不一致。

  • 外层循环 for (int i=1; i<=n; i++) 控制"行",每遍历完一行(内层循环结束),必须加一个换行,让下一行的数字从新的一行开始输出。
  1. 位置不能错:换行语句必须放在"内层循环结束后、外层循环结束前"------如果放在内层循环里,会每个数字换一行;如果漏写,所有数字连在一起,无法区分行。
    3. 配合 printf("%3d") : %3d 保证每个数字占3个字符位,换行后每行的数字对齐,最终输出整齐的矩阵(和样例格式一致)。

题目三 : 字符串的展开

思路:

这段代码的核心思路是逐字符遍历输入字符串,识别需要展开的减号片段,按照题目给定的 p1/p2/p3 规则处理后,拼接成最终结果。以下是详细步骤:

一、准备工作:工具函数与全局变量

  1. 工具函数:
  • isdig(ch) :判断字符是否是数字( '0'~'9' );

  • islet(ch) :判断字符是否是小写字母( 'a'~'z' )。

  1. 全局变量:
  • p1/p2/p3 :存储题目要求的3个参数;

  • s :输入的原始字符串;

ret :存储最终展开后的结果字符串。

二、主函数:遍历字符串,识别可展开的减号

主函数的逻辑是逐个处理字符串的每个字符,核心是判断当前字符是否是"需要展开的减号":

  1. 遍历每个字符:
cpp 复制代码
for (int i = 0;i < n;i++) {
    char ch = s[i];
  1. 跳过非减号/边界处的减号:

如果当前字符不是减号,或者减号在字符串开头/结尾(无法展开),直接将字符加入结果 ret :

cpp 复制代码
if (s[i] != '-' || i == 0 || i == n - 1) ret += ch;
  1. 判断减号是否满足展开条件:

若当前字符是减号,且不在边界,取出减号两侧的字符 left (左侧)、 right (右侧),判断是否满足"可展开"规则:

  • 两侧同为数字 且 右侧>左侧;

  • 或两侧同为小写字母 且 右侧>左侧。

满足则调用 add 函数处理中间字符,不满足则保留减号:

cpp 复制代码
if (isdig(left) && isdig(right) && left < right
    || islet(left) && islet(right) && left < right) {
    add(left, right); // 展开中间字符
} else {
    ret += ch; // 保留减号
}

三、 add 函数:按规则处理减号中间的字符

add 函数负责生成减号两侧字符之间的填充内容,并按照 p1/p2/p3 规则处理:

  1. 遍历中间字符:

从 left+1 到 right-1 ,逐个处理每个需要填充的字符:

cpp 复制代码
for (char ch = left + 1;ch < right;ch++)
  1. 处理 p1 :填充字符的类型:
  • 若 p1=2 且当前字符是字母:将小写字母转为大写( tmp -= 32 ,因为 'a'-'A'=32 );

  • 若 p1=3 :无论字母/数字,都替换为星号 * ;

  • 若 p1=1 :保持原字符不变。

cpp 复制代码
char tmp = ch;
if (p1 == 2 && islet(tmp)) tmp -= 32;
else if (p1 == 3) tmp = '*';
  1. 处理 p2 :填充字符的重复次数:

将当前处理后的字符 tmp 重复 p2 次,加入临时字符串 t :

cpp 复制代码
for (int i = 0;i < p2;i++) {
    t += tmp;
}
  1. 处理 p3 :填充内容的顺序:

若 p3=2 ,将临时字符串 t 逆序(改变填充顺序);最后将 t 加入结果 ret :

cpp 复制代码
if (p3 == 2) reverse(t.begin(), t.end());
ret += t;

四、输出结果

遍历完成后,输出最终拼接好的结果字符串 ret :

假设输入参数 p1=1, p2=1, p3=1 ,字符串包含 d-h :

  1. 主函数识别到 - ,两侧 left='d' 、 right='h' (同为字母且 h>d ),调用 add('d','h') ;

  2. add 函数遍历 e/f/g :

  • p1=1 :保持 e/f/g ;

  • p2=1 :每个字符只加1次,临时串 t="efg" ;

  • p3=1 :不逆序;

  1. ret 拼接 t ,最终 d-h 展开为 defgh 。

代码:

cpp 复制代码
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;

int p1, p2, p3, n;
string s;
string ret;

bool isdig(char ch)
{
	return ch >= '0' && ch <= '9';
}
bool islet(char ch)
{
	return ch >= 'a' && ch <= 'z';
}

void add(char left, char right)
{
	string t;

	//遍历中间的字符
	for (char ch = left + 1;ch < right;ch++)
	{
		char tmp = ch;
		//处理p1
		if (p1 == 2 && islet(tmp)) tmp -= 32;  //小写变大写
		else if (p1 == 3) tmp = '*';  //变成*号

		//处理p2
		for (int i = 0;i < p2;i++)
		{
			t += tmp;
		}
	}
	//处理p3
	if (p3 == 2) reverse(t.begin(), t.end());
	ret += t;

}

int main()
{
	cin >> p1 >> p2 >> p3 >> s;
	n = s.size();
	for (int i = 0;i < n;i++)
	{
		char ch = s[i];

		if (s[i] != '-' || i == 0 || i == n - 1) ret += ch;
		else
		{
			char left = s[i - 1], right = s[i + 1];
			//判断是否展开
			if (isdig(left) && isdig(right) && left < right
				|| islet(left) && islet(right) && left < right)
			{
				//展开
				add(left, right);
			}
			else
			{
				ret += ch;
			}
		}
	}
	cout << ret << endl;

	return 0;
}

高精度

题一 : A+B Problem

思路:

  1. ⽤字符串读入数据;
  2. 将字符串的每⼀位拆分,逆序放在数组中;
  3. 模拟列竖式计算的过程:
    a. 对应位累加;
    b. 处理进位;
    c. 处理余数。
  4. 处理结果的位数

代码:

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;

void Add(int a[], int b[], int c[])
{
	for (int i = 0;i < lc;i++)
	{
		c[i] += a[i] + b[i]; // 对应位相加,再加上进位
		c[i + 1] += c[i] / 10; // 处理进位
		c[i] %= 10; // 处理余数
	}
	if (c[lc]) lc++;
}


int main()
{
	string s1, s2;
	cin >> s1 >> s2;
	la = s1.size(), lb = s2.size();lc = max(la, lb);
	// 1. 拆分每⼀位,逆序放在数组中
	for (int i = 0;i < la; i++)
	{
		a[la - 1 - i] = s1[i] - '0';
	}
	for (int i = 0;i < lb; i++)
	{
		b[lb - 1 - i] = s2[i] - '0';
	}
	//模拟加法过程
	Add(a, b, c);

	for (int i = lc - 1;i >= 0;i--) 
		cout << c[i];

	return 0;
}
  • const int N = 1e6 + 10 :定义数组的最大长度,这里设得比500大很多,是为了给大整数的存储和进位预留足够空间,避免数组不够用。
  • int a[N], b[N] :两个数组分别存要相加的两个大整数,并且是逆序存储的------比如输入"1001",会存在a[0]=1(个位)、a[1]=0(十位)、a[2]=0(百位)、a[3]=1(千位),这样能方便从个位开始逐位计算,不用倒着找个位。
  • int c[N] :用来存加法的最终结果,和a、b一样是逆序存储,比如计算结果是1001,c数组就是c[0]=1、c[1]=0、c[2]=0、c[3]=1。
  • la, lb :分别表示两个大整数的位数,其实就是输入的两个字符串s1、s2的长度------比如s1是"123",la就是3;s2是"45",lb就是2。
  • lc :表示结果数组c的有效长度,一开始设为la和lb里的较大值(因为两个数相加,结果最少和位数多的那个数一样长),后面如果最高位有进位,lc会再加1。
  • string s1, s2 :用字符串来接收输入的大整数,因为如果直接用数字类型接收,500位的数根本存不下,字符串能原样保存每一位数字。

1. 输入与数组初始化

  • 先通过cin输入两个字符串s1、s2,然后用s1.size()、s2.size()分别得到两个数的位数la、lb。

  • 接着把字符串里的数字逆序存到数组a、b里:

比如s1是"1001"(长度la=4),循环 for (int i=0; i<la; i++) 里, a[la-1-i] = s1[i]-'0' 的意思是:把s1[0](字符'1')转成数字1,存到a[3](千位);s1[1]('0')转成0存到a[2](百位);s1[2]('0')存到a[1](十位);s1[3]('1')存到a[0](个位)------最后a数组就是[1,0,0,1],刚好对应1001的逆序。b数组的存储逻辑和a完全一样。

2. Add函数:模拟逐位加法

这个函数是核心,专门负责算a和b的和,存到c里:

  • 第一步先设lc为max(la, lb),确定结果的初始长度。

  • 然后循环 for (int i=0; i<lc; i++) ,从个位(i=0)开始,逐位算到初始lc的最高位(i=lc-1):

  • c[i] += a[i] + b[i] :当前位的和 = a的第i位数字 + b的第i位数字 + 上一步留下来的进位(c[i]一开始是0,第一次加的就是a[i[ib[i])。

  • c[i+1] += c[i] / 10 :算进位------用当前位的和除以10,商就是要进到下一位的数,加到c[i+1]里(比如和是12,进位就是1,加到下一位)。

  • c[i] %= 10 :当前位只留个位数字------用和除以10的余数作为当前位的结果(比如和是12,余数2就是当前位的数)。

  • 循环结束后,判断 c[lc] 是不是0:如果不是0,说明最高位相加后产生了进位(比如999+1,最高位加完进位1),这时候就把lc加1,让结果长度包含这个进位位。

3. 输出结果

因为c数组是逆序存的(个位在c[0],最高位在c[lc-1]),所以输出时要从lc-1(最高位)循环到0(个位),依次输出每一位数字,拼起来就是正确的结果。

注意点:

1. 为什么要写 s1[i] - '0' ?------ 字符转数字的关键

因为 s1、s2 是字符串类型,里面存的每一个"数字"本质是字符(比如字符 '1'、'9'),而不是真正的整数 1、9。

计算机中,字符是用 ASCII 码存储的:字符 '0' 的 ASCII 码是 48,'1' 是 49,'2' 是 50...... 直到 '9' 是 57。

所以 s1[i] - '0' 的本质是:用字符的 ASCII 码减去 '0' 的 ASCII 码,把字符转成对应的整数------比如 '5' - '0' = 53 - 48 = 5,刚好得到数字 5。

如果不写 - '0' ,直接用 s1[i] 赋值给数组,存的会是 ASCII 码(比如 '1' 存成 49),后续加法就完全错了。

2. 加法函数中, += 和 %= 能不能换成 + 和 % ?------ 必须用复合赋值,否则漏进位

不能直接换,核心是要保留"上一轮的进位":

  • 先看 c[i] += a[i] + b[i] :这里的 += 不是简单的"加",而是"当前位的和 + 上一轮存到 c[i] 的进位"。

比如上一轮计算时, c[i] 可能已经存了前一位传来的进位(比如 i=1 时,c[1] 可能被 i=0 的进位 c[1] += 1 赋值过)。如果换成 c[i] = a[i] + b[i] ,就会把之前的进位覆盖掉,导致进位丢失。

  • 再看 c[i] %= 10 : %= 是"先取余,再把结果存回 c[i]"。

如果换成 c[i] = c[i] % 10 ,功能上完全一样(只是写法不同);但如果直接写 c[i] % 10 而不加赋值,取余后的结果没存回 c[i],c[i] 还是原来的"和+进位"的数值(比如 12),后续输出就会错。

3. 为什么要 lc++ ?------ 给最高位的进位"腾位置"

lc 是结果数组 c 的有效长度(即最终输出的位数),初始 lc = max(la, lb) (比如 999+2,初始 lc=3)。

但两个数相加可能产生"最高位进位"(比如 999+2=1001,原来 3 位,加完变成 4 位,多了最高位的 1)。

循环结束后, c[lc] 存的就是最高位的进位(比如 999+2 中,c[3] = 1)。

所以 if (c[lc]) lc++ 的作用是:如果最高位有进位(c[lc]≠0),就把结果长度加 1,让这个进位成为"新的最高位"------否则输出时会漏掉这个进位(比如 1001 只输出 001)。

如果不写 lc++ ,最高位的进位就不会被输出,结果必然少一位,完全错误。


题二 : A-B Problem

思路:

代码:

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e6 + 10;
int la, lb, lc;
int a[N], b[N], c[N];

bool cmp(string& s1, string& s2)
{
	//先比较长短
	if (s1.size() != s2.size())
		return s1.size() < s2.size();
	//再按照 字典序 的方式进行比较
	return s1 < s2;
}

void sub(int a[], int b[], int c[])
{
	for (int i = 0;i < lc;i++)
	{
		c[i] += a[i] - b[i];
		if (c[i] < 0)
		{
			c[i + 1] -= 1;
			c[i] += 10;
		}
	}
	//处理前导零
	while (lc > 1 && c[lc - 1] == 0) lc--;
}

int main()
{
	string s1, s2;
	cin >> s1 >> s2;
	//先比大小
	if(cmp(s1,s2))
	{
		swap(s1, s2);
		cout << '-';
	}
	//拆分每一位,逆序放入数组中
	la = s1.size(), lb = s2.size(), lc = max(la, lb);
	for (int i = 0;i < la;i++)
	{
		a[la - 1 - i] = s1[i] - '0';
	}
	for (int i = 0;i < lb;i++)
	{
		b[lb - 1 - i] = s2[i] - '0';
	}
	//模拟减法
	sub(a, b, c);

	for (int i = lc - 1;i >= 0;i--)
		cout << c[i];

	return 0;
}

我们用 8-12 这个例子,走一遍代码的执行流程:

步骤1:读入字符串

输入 s1="8" , s2="12" 。

步骤2:比较大小(确定被减数/减数)

调用 cmp(s1, s2) :

  • s1长度=1 , s2长度=2 → s1.size() < s2.size() ,所以 cmp返回true 。

  • 执行 swap(s1, s2) → 此时 s1="12" , s2="8" ;同时输出负号 - 。

步骤3:字符串逆序存入数组

  • la = s1.size()=2 , lb = s2.size()=1 , lc = max(2,1)=2 。

  • 处理 s1="12" :

a[2-1-0] = a[1] = '1'-'0'=1 ;

a[2-1-1] = a[0] = '2'-'0'=2 ;
所以 a数组 : a[0]=2, a[1]=1

  • 处理 s2="8" :

b[1-1-0] = b[0] = '8'-'0'=8 ;( b[1] 默认为0)
所以 b数组 : b[0]=8, b[1]=0 。

步骤4:模拟减法(sub函数)

遍历 i=0 到 i=1 :

  • i=0 :

c[0] += a[0]-b[0] = 2-8 = -6 ;

因为 -6 < 0 ,所以 c[1] -=1 ( c[1] 变为 -1 ), c[0] +=10 → c[0]=4 。

  • i=1 :

c[1] += a[1]-b[1] = (-1) + 1-0 = 0 ;

无需借位。

处理前导零: lc=2 , c[1]=0 → 执行 lc-- → lc=1 。

步骤5:输出结果

逆序输出 c数组 (从 lc-1=0 到 0 ):输出 c[0]=4 。

最终整体输出: -4 (对应 8-12=-4 )。

注意点:

注意点1:提前输出负号

  • 为什么要提前输出?

因为我们会把"小数减大数"转换成"大数减小数"来计算(避免中间出现负数),所以只有在交换了被减数和减数时(即原数是小数减大数),才需要在开头输出负号。

  • 易错点:不要在计算完结果后再补负号(容易忘记或逻辑混乱),而是在确定需要交换两个数的同时就输出负号。

注意点2:先比字符串长度,再比字典序

  • 长度优先的原因:数字的位数越多,数值一定越大(比如"123"是3位,"45"是2位,123>45)。

  • 字典序的适用场景:只有当两个数的位数相同时,字符串的字典序才等价于数字的大小(比如"123"和"124",字典序"123"<"124",对应数字123<124)。

  • 易错点:不能直接对任意长度的字符串用字典序比较(比如"9"和"10",字典序"9">"10",但实际数字9<10)。

注意点3:前导零的处理

  • 处理时机:要在减法计算完成后再处理前导零(计算过程中高位的0是必要的,不能提前删)。

  • 处理规则:从结果数组的最高位(数组末尾)开始检查,若为0则缩短结果长度,直到遇到非0数字;但要保证至少保留1位(比如结果是0时,不能删成空)。

  • 易错点:如果不处理前导零,会输出"00901"这类不合法的数字;如果处理过度,可能把"0"删成空字符串。

题三 : A*B Problem

思路:

代码:

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N], b[N], c[N];
int la, lb, lc;

void mul(int a[], int b[], int c[])
{
	//无进位相乘 , 然后相加
	for (int i = 0;i < la;i++)
	{
		for (int j = 0;j < lb;j++)
		{
			c[i + j] += a[i] * b[j];
		}
	}
	//处理进位
	for (int i = 0;i < lc;i++)
	{
		c[i + 1] += c[i] / 10;
		c[i] %= 10;
	}
	//处理值为零时
	while (lc > 1 && c[lc - 1] == 0) lc--;
}

int main()
{
	string s1, s2;
	cin >> s1 >> s2;

	la = s1.size(), lb = s2.size(), lc = la + lb;
	for (int i = 0;i < la;i++)
	{
		a[la-1-i] = s1[i]-'0';
	}
	for (int i = 0;i < lb;i++)
	{
		b[lb - 1 - i] = s2[i] - '0';
	}

	mul(a, b, c);

	for (int i = lc - 1;i >= 0;i--)
	{
		cout << c[i];
	}
	return 0;
}

普通数据类型存不下2000位的大整数,所以用数组逆序存储大整数的每一位,然后像列竖式乘法一样:

  1. 把其中一个数的每一位,分别和另一个数的每一位相乘;

  2. 把乘积"错位"累加到结果数组的对应位置;

  3. 最后统一处理所有位置的进位,得到最终结果。

mul 函数:模拟竖式乘法
(1)逐位相乘 + 错位累加

循环 for (int i=0; i<la; i++) (遍历 a 的每一位),嵌套 for (int j=0; j<lb; j++) (遍历 b 的每一位):

  • a[i] 是 a 的第 i 位(对应原数的"第i位",比如 i=0 是个位);

  • b[j] 是 b 的第 j 位;

  • 它们的乘积要存到 c[i+j] 中(错位累加):比如 a的个位(i=0)×b的个位(j=0) ,结果存到 c[0] ; a的个位×b的十位(j=1) ,结果存到 c[1] ; a的十位(i=1)×b的个位 ,结果也存到 c[1] ------这和竖式乘法中"相同数位对齐"的逻辑一致。

  • 用 += 而不是 = :因为多个乘积会加到同一个 c[i+j] 位置(比如 a[0]×b[1] 和 a[1]×b[0] 都要加到 c[1] ),如果用 = 会覆盖之前的结果。
    (2)统一处理进位

循环 for (int i=0; i<lc; i++) ,从个位到最高位处理每一位的进位:

  • c[i+1] += c[i] / 10 :把当前位的"十位及以上部分"(即 c[i]/10 )作为进位,加到下一位;

  • c[i] %= 10 :当前位只保留"个位部分"(即 c[i]%10 )。

这样一次循环就能把所有位置的进位处理完。
(3)去掉结果的前导零

用 while (lc>1 && c[lc-1]==0) :

  • 如果结果的"最高位"是0(比如 100×100=10000 ,初始 lc=6 ,但 c[5]=0 ),就把 lc 减1,直到最高位不是0或只剩1位(避免输出 0000 这种无效结果)。

注意点:

1. 数组存储的"逆序"必须严格对应"数位"

  • 逆序的核心是把"个位存在数组下标0的位置"(比如输入 123 , a[0]=3 是个位, a[1]=2 是十位, a[2]=1 是百位)。

  • 如果顺序存反(比如把高位存在下标0),后续的 i+j 错位累加逻辑会完全混乱,导致数位对齐错误。
    2. 相乘阶段必须用 += 而不是 =

  • 错误写法: c[i+j] = a[i] * b[j] (会覆盖之前的乘积);

  • 正确写法: c[i+j] += a[i] * b[j] (累加所有错位的乘积)。

比如 a[0]×b[1] 和 a[1]×b[0] 都要加到 c[1] ,如果用 = 会只保留最后一次乘积,结果必然错误。
3. lc 的初始值必须是 la + lb

两个数相乘,结果的最大位数是"位数之和"(比如3位×2位最多5位)。如果 lc 设得更小,会导致高位的乘积或进位被截断,结果丢失;设得更大则会多一些前导零,但后续的去零逻辑可以处理。
4. 进位循环的范围必须覆盖 lc

  • 进位循环要写 for (int i=0; i<lc; i++) (而不是 i<=lc 或更小的范围),因为 lc 是结果的最大位数,所有可能的进位都在这个范围内。

  • 如果循环范围不够,高位的进位会被遗漏,导致结果错误(比如999×999的进位会传到很高位)。
    5. 去前导零的逻辑不能省略,且要在"进位之后"执行

  • 必须等所有进位处理完,再去前导零(否则会把合法的高位0误删)。

  • 条件 lc>1 是为了避免"结果本身是0"时被误删(比如输入 0 和 123 ,结果是 0 ,不能把唯一的 0 删掉)。


题四 : A/B Problem

思路:

代码:

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e6 + 10;
int a[N], b, c[N];
int la, lc;

void sub(int a[], int b, int c[])
{
	//标记每次除完之后的余数
	long long t = 0;
	for (int i = la - 1;i >= 0;i--)
	{
		//计算当前的被除数
		t = t * 10 + a[i];
		c[i] = t / b;
		t %= b;
	}
	//处理前导零
	while (lc > 1 && c[lc - 1] == 0) lc--;
}

int main()
{
	string s1;
	cin >> s1 >> b;
	la = s1.size();

	for (int i = 0;i < la;i++)
	{
		a[la - 1 - i] = s1[i] - '0';
	}

	lc = la;
	sub(a, b, c);

	for (int i = lc - 1;i >= 0;i--)
		cout << c[i];

	return 0;
}

比如 100÷5 的流程:

步骤1:读入与数组存储

输入 s1="100" , b=5 。

la = s1.size()=3 ,逆序存入数组 a :

  • a[3-1-0] = a[2] = '1'-'0' = 1

  • a[3-1-1] = a[1] = '0'-'0' = 0

  • a[3-1-2] = a[0] = '0'-'0' = 0

此时 a数组 : a[0]=0, a[1]=0, a[2]=1 (逆序后的 100 )。

步骤2:调用 sub 函数

lc=3 ,初始化余数 t=0 ,从 i=la-1=2 (数组 a 的高位)遍历到 i=0 :

  • i=2:

t = t*10 + a[2] = 0*10 + 1 = 1

c[2] = t / b = 1 / 5 = 0

t = t % b = 1 % 5 = 1

  • i=1:

t = 1*10 + a[1] = 10 + 0 = 10

c[1] = 10 / 5 = 2

t = 10 % 5 = 0

  • i=0:

t = 0*10 + a[0] = 0 + 0 = 0

c[0] = 0 / 5 = 0

t = 0 % 5 = 0

此时 c数组 : c[0]=0, c[1]=2, c[2]=0 。

步骤3:处理前导零

lc=3 ,检查 c[lc-1]=c[2]=0 ,执行 lc-- ( lc=2 );

再检查 c[1]=2≠0 ,停止处理。

步骤4:输出(修正循环条件为 i >= 0 )

从 i=lc-1=1 遍历到 i=0 ,输出 c[1]=2 、 c[0]=0 ,最终结果为 20 (即 100÷5=20 )。

注意点:

注意点1:为什么b用整形变量,用数组?

因为你的代码实现的是 "高精度除以低精度"(即:大数÷小数),不是"高精度除以高精度"。

  • b 是"小数":题目中 b 的范围没超过整形( int )的存储上限(比如 b≤1e9 ),直接用 int 存就行;

  • 若 b 也是大数(比如 b=1234567890123 ),就不能用 int 了,必须也用数组存,那就是"高精度÷高精度",逻辑会更复杂(需要用减法模拟除法)。

注意点2:为什么除法函数里面的for循环i从大到小?

因为除法要 从原数的高位开始算(和竖式除法一致)。

  • 数组 a 是逆序存的(比如 100 存成 [0,0,1] ),原数的高位对应数组的"大下标"( a[2] 是百位1);

  • 所以 i从la-1(大下标)到0(小下标) ,就是从原数的高位→低位遍历,正好匹配竖式除法的计算顺序(先算百位,再算十位,最后算个位)。
    注意3:为什么用long long?int可以吗?

不可以,会溢出!

t 的逻辑是"之前的余数×10 + 当前位",假设 a 的当前位是9,之前的余数是 1e9 ( int 最大值约2e9):

  • 若用 int : t = 1e9 *10 +9 = 10000000009 ,远超 int 的存储上限(2147483647),会溢出出错;

  • 用 long long :存储上限是 9e18 ,足够容纳"余数×10+当前位"的结果(就算余数是 1e9 ,×10+9也才 1e10 ,远小于 9e18 )。

注意点4:t的逻辑(核心!)

t 是 "当前正在计算的被除数",本质是模拟竖式除法中"余数+落位"的过程,一步一步拆:

  1. 初始化 t=0 :刚开始没有余数;

  2. 每一步 t = t*10 + a[i] :把"上一步的余数"×10(相当于竖式里"把下一位落下来",比如余数1→10,再加上当前位0→10);

  3. 计算 c[i] = t / b :当前位的商(比如10÷5=2);

  4. 更新 t = t % b :当前步的余数(比如10%5=0,作为下一步的初始余数)。

举个例子( 100÷5 ):

  • 第一步(百位1):t=0×10+1=1 → 商0 → 余数1;

  • 第二步(十位0):t=1×10+0=10 → 商2 → 余数0;

  • 第三步(个位0):t=0×10+0=0 → 商0 → 余数0;


枚举

题一 : 铺地毯

思路:

代码:

cpp 复制代码
#include<iostream>
using namespace std;

const int N = 1e4 + 10;
int a[N], b[N], g[N], k[N];
int x, y;
int n;  //表示有几张地毯

int find()
{
	//从后往前枚举
	for (int i = n;i >= 1;i--)
	{
		//判断是否覆盖
		if (a[i] <= x && b[i] <= y && a[i] + g[i] >= x && b[i] + k[i] >= y)
		{
			return i;
		}
	}
	return -1;
}

int main()
{
	cin >> n;
	for (int i = 1;i <= n;i++)
		cin >> a[i] >> b[i] >> g[i] >> k[i];
	cin >> x >> y;

	cout << find() << endl;

	return 0;
}

注意点:

  1. 全局变量的含义

代码里定义的全局数组:

  • a[N] :存储第 i 张地毯左下角的x坐标(对应题目里的 a );

  • b[N] :存储第 i 张地毯左下角的y坐标(对应题目里的 b );

  • g[N] :存储第 i 张地毯在x轴方向的长度(对应题目里的 g );

  • k[N] :存储第 i 张地毯在y轴方向的长度(对应题目里的 k );

  • 全局变量 x,y :存储待查询点的坐标;

  • 全局变量 n :存储地毯的总数。

  1. find函数中if语句的逻辑

if (a[i] <= x && b[i] <= y && a[i] + g[i] >= x && b[i] + k[i] >= y) 是用来判断第i张地毯是否覆盖了点(x,y),逻辑是:

  • 地毯的x范围是 [a[i], a[i[ig[i]] (左下角x到x+长度),所以点的x要满足 a[i] ≤ x ≤ a[i[ig[i] ;

  • 地毯的y范围是 [b[i], b[i[ik[i]] (左下角y到y+长度),所以点的y要满足 b[i] ≤ y ≤ b[i[ik[i] ;

  • 同时满足这四个条件,说明点在第i张地毯的范围内。

  1. 变量与题目如何串联

题目是"按编号从小到大铺地毯,后铺的覆盖先铺的,求点上方最上面的地毯编号",代码的串联逻辑:

  1. 输入时,把每张地毯的 a,b,g,k 存在对应的全局数组里( a[i] 对应第i张的左下角x,依此类推);

  2. 输入查询点的 x,y ,存在全局变量里;

  3. find 函数从后往前枚举地毯(因为后铺的在上面,找到第一个覆盖点的就是最上面的 );

  4. 用if语句判断当前地毯是否覆盖点,找到则返回编号,全枚举完没找到就返回-1。


题二 : 回文日期

思路1:

代码:

cpp 复制代码
#include<iostream>
using namespace std;

int x, y;
int day[] = { 0,31,29,31,30,31,30,31,31,30,31,30,31 };

int main()
{
	cin >> x >> y;
	int ret = 0;
	//枚举日月的组合
	for (int i = 1;i <= 12;i++)
	{
		for (int j = 1;j <= day[i];j++)
		{
			int k = j % 10 * 1000 + j / 10 * 100 + i % 10 * 10 + i / 10;
			int num = k * 10000 + i * 100 + j;
			if (x <= num && num <= y)
				ret++;
		}
	}
	cout << ret << endl;

	return 0;
}

该思路本质是利用回文日期的结构特性,先构造"合法的回文日期",再验证是否在目标范围内,具体逻辑可以拆解为:

因为8位回文日期的格式是 YYYYMMDD ,且必须满足"回文",所以:

YYYY 是 DDMM 的反转(比如月日是 0102 ,则年份必须是 2010 ,这样整体是 20100102 ,满足回文)。

所以思路是:

  1. 枚举所有合法的月日组合(比如1月1日、1月2日...12月31日,只枚举真实存在的月日);

  2. 根据月日构造对应的回文年份(把月日的字符串反转,得到年份);

  3. 拼接成完整的8位回文日期(年份+月日);

  4. 验证这个日期是否在输入的 x-y 范围内,若是则计数。

为什么这个思路可行?

  • 回文日期的结构决定了:只要月日合法,且对应的年份构造正确,得到的日期一定是回文的(不需要额外判断回文);

  • 只枚举"合法的月日",避免了构造无效的回文日期(比如月日是 1301 这种不存在的月份);

  • 最后只需要验证日期是否在目标区间,逻辑简洁且效率高。

注意点:

为什么数组中对应的二月份是29天而不是28天?

因为每个月日组合对应的回文日期是唯一的(因为年份由月日的各位数字反向映射决定),不存在"一个月日对应多个年份"的情况。

所以当月日是 0229 时,只能构造出 92200229 ,然后只需要判断 9220 是否是闰年即可(又因为9220是闰年,所以这个日期合法)。

这个思路的优势是避免了遍历大量无效日期,只枚举合法的月日组合,再反向构造年份,效率很高。

思路2:

代码:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 判断是否为闰年
bool isLeap(int year) {
    if (year % 400 == 0) return true;
    if (year % 100 == 0) return false;
    if (year % 4 == 0) return true;
    return false;
}

// 判断日期是否合法
bool isValidDate(int year, int month, int day) {
    // 月份范围1-12
    if (month < 1 || month > 12) return false;
    // 各月份的天数(先按平年处理2月)
    int daysInMonth[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
    // 闰年2月调整为29天
    if (month == 2 && isLeap(year)) {
        daysInMonth[2] = 29;
    }
    // 日期范围1-当月最大天数
    return (day >= 1 && day <= daysInMonth[month]);
}

// 判断数字是否为回文(8位)
bool isPalindrome(int num) {
    string s = to_string(num);
    // 8位数字才需要判断(题目中输入是8位,枚举的也是8位)
    for (int i = 0; i < 4; i++) {
        if (s[i] != s[7 - i]) {
            return false;
        }
    }
    return true;
}

int main() {
    int x, y;
    cin >> x >> y;
    int count = 0;

    // 枚举x到y之间的所有数字
    for (int num = x; num <= y; num++) {
        // 第一步:判断是否是回文
        if (!isPalindrome(num)) {
            continue;
        }
        // 第二步:拆分为年、月、日
        int year = num / 10000;       // 前4位是年
        int month = (num / 100) % 100; // 中间2位是月
        int day = num % 100;          // 最后2位是日
        // 第三步:验证日期是否合法
        if (isValidDate(year, month, day)) {
            count++;
        }
    }

    cout << count << endl;
    return 0;
}

思路3:

代码:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 判断是否为闰年
bool isLeap(int year) {
    if (year % 400 == 0) return true;
    if (year % 100 == 0) return false;
    if (year % 4 == 0) return true;
    return false;
}

// 判断日期是否合法
bool isValidDate(int year, int month, int day) {
    // 月份范围1-12
    if (month < 1 || month > 12) return false;
    // 各月份的天数(先按平年处理2月)
    int daysInMonth[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
    // 闰年2月调整为29天
    if (month == 2 && isLeap(year)) {
        daysInMonth[2] = 29;
    }
    // 日期范围1-当月最大天数
    return (day >= 1 && day <= daysInMonth[month]);
}

// 判断数字是否为回文(8位)
bool isPalindrome(int num) {
    string s = to_string(num);
    // 8位数字才需要判断(题目中输入是8位,枚举的也是8位)
    for (int i = 0; i < 4; i++) {
        if (s[i] != s[7 - i]) {
            return false;
        }
    }
    return true;
}

int main() {
    int x, y;
    cin >> x >> y;
    int count = 0;

    // 枚举x到y之间的所有数字
    for (int num = x; num <= y; num++) {
        // 第一步:判断是否是回文
        if (!isPalindrome(num)) {
            continue;
        }
        // 第二步:拆分为年、月、日
        int year = num / 10000;       // 前4位是年
        int month = (num / 100) % 100; // 中间2位是月
        int day = num % 100;          // 最后2位是日
        // 第三步:验证日期是否合法
        if (isValidDate(year, month, day)) {
            count++;
        }
    }

    cout << count << endl;
    return 0;
}

题三 : 扫雷

思路:

代码:

cpp 复制代码
#include<iostream>
using namespace std;
const int N = 1e4 + 10;
int n;
int a[N], b[N];

int check1()
{
	a[1] = 0;
	for (int i = 2;i <= n + 1;i++)
	{
		a[i] = b[i - 1] - a[i - 1] - a[i - 2];
		if (a[i] < 0 || a[i]>1) return 0;
	}
	if (a[n + 1] == 0) return 1;
	else return 0;
}
int check2()
{
	a[1] = 1;
	for (int i = 2;i <= n + 1;i++)
	{
		a[i] = b[i - 1] - a[i - 1] - a[i - 2];
		if (a[i] < 0 || a[i]>1) return 0;
	}
	if (a[n + 1] == 0) return 1;
	else return 0;
}

int main()
{
	cin >> n;
	for (int i = 1;i <= n;i++) cin >> b[i];
	
	int ret = 0;
	ret += check1();
	ret += check2();

	cout << ret << endl;

	return 0;
}
  • a[i] :第一列第 i 个格子的状态(0=非雷,1=雷);
  • b[i] :第二列第 i 个格子的值(即周围雷的数量);
  • check1() / check2() :分别枚举第一列第1个格子是0(非雷)/1(雷)的情况。

注意点:

  1. 原代码中 for 循环到 n+1 的原因
  • 你的思路是:把第一列的"虚拟第 n+1 个格子"也纳入计算------因为第二列第 i 个值对应第一列 i-1、i、i+1 三个格子的雷数(8连通)。
  • 比如第二列第 n 个值,对应的是第一列 n-1、n、n+1 三个格子的雷数。但第一列实际只有 n 个格子,所以你想通过" a[n+1] 必须是0(无雷)"来约束最后一个位置的合法性。
  1. 原代码中 if (a[n+1] == 0) 的意义
  • 这里的 a[n+1] 是虚拟的"第n+1个格子",你认为"第一列没有第n+1个格子,所以它必须是0",以此验证第二列第n个值的约束是否成立。

题四 : 子集 (二进制枚举)

思路:

用二进制枚举法解决"子集"问题,核心是用整数的二进制位表示元素的选择状态。

对于长度为 n 的数组,每个元素有"选"或"不选"两种状态,正好可以用 n位二进制数 表示一种选择方案:

  • 二进制数的每一位对应数组的一个元素;
  • 某一位是 1 表示"选这个元素",是 0 表示"不选"。

例如数组 [1,2,3] ( n=3 ):

  • 二进制 000 → 所有位是0 → 空集;
  • 二进制 001 → 第0位是1 → 选第0个元素 → {1};
  • 二进制 010 → 第1位是1 → 选第1个元素 → {2};
  • 二进制 011 → 第0、1位是1 → 选第0、1个元素 → {1,2};
  • ...以此类推,直到二进制 111 → 选所有元素 → {1,2,3}。
  1. for(int st = 0; st < (1 << n); st++) :
  • 1 << n 等价于 2^n(左移n位),所以循环会从 0 遍历到 2^n - 1,覆盖所有 n位二进制数 的可能(正好对应所有子集的选择状态)。
  1. (st >> i) & 1 :
  • st >> i :将整数 st 右移 i 位,把第 i 位移动到最低位;
  • & 1 :和1做与运算,取出最低位的值(0或1);
  • 整体作用:判断 st 的第 i 位是0还是1,从而决定是否选择 nums[i] 。

示例验证(以 nums = [1,2,3] 为例)

  • 当 st = 0 (二进制 000 ):所有位都是0 → tmp 为空集 → 加入结果;

  • 当 st = 1 (二进制 001 ):第0位是1 → tmp = [1] → 加入结果;

  • 当 st = 2 (二进制 010 ):第1位是1 → tmp = [2] → 加入结果;

  • 当 st = 3 (二进制 011 ):第0、1位是1 → tmp = [1,2] → 加入结果;

  • ...直到 st = 7 (二进制 111 ):所有位是1 → tmp = [1,2,3] → 加入结果。

代码:

相关推荐
代码村新手2 小时前
数据结构-二叉树
数据结构
姓蔡小朋友2 小时前
redis GEO数据结构、实现附近商铺功能
数据结构·数据库·redis
Live&&learn2 小时前
数据结构vs 内存结构
数据结构·操作系统·内存结构
buyue__2 小时前
C++实现数据结构——队列和栈
数据结构
执笔论英雄3 小时前
【大模型训练】forward_backward_func返回多个micro batch 损失
开发语言·算法·batch
太理摆烂哥3 小时前
哈希表实现
数据结构·哈希算法·散列表
序属秋秋秋4 小时前
《Linux系统编程之进程基础》【进程优先级】
linux·运维·c语言·c++·笔记·进程·优先级
草莓熊Lotso4 小时前
C++ STL map 系列全方位解析:从基础使用到实战进阶
java·开发语言·c++·人工智能·经验分享·网络协议·everything
_F_y4 小时前
C++IO流
c++