2^n 种符号方案只有 k 种余数状态:用布尔数组保存可达余数集合,逐个数字平移出 ±a_i 两支,O(nk) 判定余数 0 是否可达。
OJ: roj
题目 ID: 1195
难度:普及-
标签:动态规划位运算数学python
创建: 2026-09-29 23:03
更新: 2026-10-05 05:11
题目描述
给定 + 或 - 后求和;只要 YES 或 NO。样例输入 3 2 / 1 2 4,样例输出 NO(
思路
reach[r] 记录处理完前 reach[0] = 1;每加入 reach[0] 即可。因为只看余数,可以先把每个
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-29 23:03
# update_at: 2026-09-29 23:08
import sys
def shift_ring(mask: int, step: int, k: int, full: int) -> int:
"""把「可达余数集合」整体沿环平移 step 步,超出的位绕回低位(循环移位)。
mask 的第 r 位为 1 表示余数 r 可达;每位移到 r+step,所以 +a 是环形左移 a 位,-a 是环形左移 k-a 位。
"""
step %= k # 归约到 [0,k):既完成 a 取模,也把 -a 折成 k-(a mod k)
return (mask << step | mask >> (k - step)) & full
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
next(data) # n:循环直接迭代剩余整数,这里只跳过它
k = next(data)
full = (1 << k) - 1 # k 个余数位全为 1 的封顶掩码
reach = 1 # 只有余数 0 可达(空序列和为 0)
for a in data: # 逐个数字决定前面放 + 还是 -
reach = shift_ring(reach, a, k, full) | shift_ring(reach, -a, k, full)
print("YES" if reach & 1 else "NO") # 第 0 位仍是 1 就说明存在和为 k 倍数的符号方案
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 05:11
* update_at: 2026-10-05 05:11
*/
// main.cpp:判断整除。2^n 种符号方案只有 k 个余数状态,保留「可达余数集合」逐个数字推进。
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 10005;
const int MAXK = 105;
ll n, k;
ll a[MAXN]; // a[i] 已归约到 [0, k),加号减号只关心余数
char reach[MAXK]; // reach[r] = 1 表示处理完当前前缀后余数 r 可达
char nxt[MAXK]; // 加入 a[i] 之后的可达余数集合
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
a[i] %= k; // 只看余数:s * a 与 s * (a mod k) 模 k 同余
}
reach[0] = 1; // 空序列的和为 0
for (ll i = 1; i <= n; i++) {
for (ll r = 0; r < k; r++) nxt[r] = 0;
for (ll r = 0; r < k; r++) {
if (!reach[r]) continue;
nxt[(r + a[i]) % k] = 1; // 第 i 个数前放 +
nxt[(r - a[i] + k) % k] = 1; // 第 i 个数前放 -
}
for (ll r = 0; r < k; r++) reach[r] = nxt[r];
}
if (reach[0])
cout << "YES" << "\n";
else
cout << "NO" << "\n";
return 0;
}