[语言月赛 202311] 方程求解 的题解
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求对 个形如
的一元一次方程分别求出解,再回答
次询问:区间
内有多少个不同的整数
至少是其中一个方程的解。核心解法是移项得到
,把每个解标记进桶中(同一个解重复出现也只标记一次,天然完成去重),再对桶求前缀和,使每次询问在
时间内回答。
分析
核心观察
每个方程的解由系数唯一确定,移项得 ,即
。询问统计的是不同
的个数,同一个
无论被多少个方程命中都只贡献
,用一个「该值是否出现过」的桶标记即可自动去重。
思路
设第 个方程为
,两边同时减去
得
,于是
题面保证每个解 都是正整数,且由
知
,除法总可以进行。
从方程字符串 中取出三个数是本题唯一的实现难点:
可以带负号(如
中
),
前面的 + 或 − 必须并入
的取值(如
中
,若误读成
便会得到
而不是正确的
),
同样可能为负。读入时可以借助
sscanf,把 x 与 = 当作字面字符写进格式串,让三个 依次读入
,符号由
自行处理;也可以手写解析,逐个字符跳过 x 与 =,遇到 + 或 − 时把符号并入紧随其后的整数。
求解结果只需要记录出现性:由 得
,又
,故解最大可以取到
(例如
的解为
)。询问保证
,超过
的解永远不会落入任何询问区间,因此桶只需覆盖
,标记前判断
即可,既不会越界也不会漏计。
记 表示
至少是一个方程的解,
表示不是,并令
则询问 的答案就是
,即区间内被标记的位置个数。若不做前缀和,每次询问直接扫描
需要
,在本题
的数据范围下同样可以通过;真正的陷阱在于去重,若直接统计「解落在区间内的方程个数」,样例
中
被三个方程命中,会被多算两次。
具体示例
以样例 为例,三个方程
、
、
的解依次为
、
、
,桶中只有下标
被标记,前缀和在
上的取值为
。四次询问分别为:
得
,
得
,
得
,
得
,与样例输出一致。
以样例 为例,五个方程的解依次为
,去重后只有
与
两个位置被标记:
被三个方程命中、
被两个方程命中,但都各自只计一次。三次询问
、
、
的答案分别为
、
、
。
算法步骤
- 读入
n与q。 - 逐个读入方程字符串,解析出三个可带符号的整数
a、b、c,其中a的负号、b前的 + 或 − 都要并入各自的值。 - 计算
x;若
,把
mark的第x项置为(同一个
x重复出现也只置为)。
- 对
mark求前缀和,结果存入pre。 - 对每次询问读入
L、R,计算ans并输出。
复杂度分析
- 时间:
。解析
个方程并标记需要
,求前缀和需要
,每次询问为
。
- 空间:
。桶与前缀和数组各需要约
个元素,不计输出缓冲。
实现注意事项
- 符号必须跟着数字一起读:
可以带负号,
前的 + 或 − 必须并入
,
同样可能为负。以
为例,
,解得
;若把
读成
,就会算出
这样的错误结果。
- 移项方向不能反:正确式子是
,写成
几乎全错。
- 去重要靠桶完成:题目问的是不同
的个数,同一个解只能计一次,直接统计「解落在区间内的方程个数」会在样例
上出错。
- 桶的下标范围要留足:解最大可达
,如果桶只开到
,标记前必须先判断
,否则会写到数组外。
- 只标记正整数解:标记条件是
,解为
或负数的方程不应进入桶(本题数据保证解为正整数,此处仅为稳妥)。
- 输入是一行一个方程字符串,用格式化读入时要按 x 与 = 这两个字面字符切分,手写解析则要显式吃掉负号,不要漏掉
前的正号。
- 输出最多约
行,C++ 应把答案拼成字符串后一次性写出,Python 应先把答案收集到列表再用换行符连接输出,避免逐行刷新缓冲区。
- 询问保证
,前缀和数组开到下标
就够用,无需特判
或
越过上界的情况。
源代码
#include <cstdio>
#include <string>
#include <vector>
int main() {
int n = 0, q = 0;
if (std::scanf("%d%d", &n, &q) != 2) return 0;
const int MAXV = 1000; // 询问保证 R <= 1000,更大的解不会被问到
std::vector<char> mark(MAXV + 1, 0);
char buf[64];
for (int i = 0; i < n; ++i) {
if (std::scanf("%63s", buf) != 1) return 0;
int a = 0, b = 0, c = 0;
// 按 'x' 与 '=' 两个字面字符切分,符号由 %d 自动读入
if (std::sscanf(buf, "%dx%d=%d", &a, &b, &c) != 3) continue;
int num = c - b; // a x = c - b
if (a == 0 || num % a != 0) continue;
int x = num / a;
if (x >= 1 && x <= MAXV) mark[x] = 1; // 同一个解只标记一次
}
std::vector<int> pre(MAXV + 1, 0); // pre[i] = 1..i 中被标记的个数
for (int i = 1; i <= MAXV; ++i) pre[i] = pre[i - 1] + mark[i];
std::string out;
out.reserve((size_t)q * 6);
for (int i = 0; i < q; ++i) {
int L = 0, R = 0;
if (std::scanf("%d%d", &L, &R) != 2) break;
out += std::to_string(pre[R] - pre[L - 1]);
out += '\n';
}
std::fwrite(out.data(), 1, out.size(), stdout);
return 0;
}
import sys
import re
def main():
data = sys.stdin.read().split()
pos = 0
n = int(data[pos]); pos += 1
q = int(data[pos]); pos += 1
MAXV = 1000 # 询问保证 R <= 1000
mark = [0] * (MAXV + 1)
for _ in range(n):
a, b, c = map(int, re.findall(r'[-+]?\d+', data[pos]))
pos += 1
num = c - b # a x = c - b
if a != 0 and num % a == 0:
x = num // a
if 1 <= x <= MAXV:
mark[x] = 1 # 同一个解只标记一次
pre = [0] * (MAXV + 1) # pre[i] = 1..i 中被标记的个数
for i in range(1, MAXV + 1):
pre[i] = pre[i - 1] + mark[i]
out = []
for _ in range(q):
L = int(data[pos]); R = int(data[pos + 1]); pos += 2
out.append(str(pre[R] - pre[L - 1]))
sys.stdout.write('\n'.join(out) + '\n')
main()
评论