专栏文章
题解:P14360 [CSP-J 2025] 多边形 / polygon(民间数据)
P14360题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @minfnlu2
- 此快照首次捕获于
- 2025/12/02 01:38 3 个月前
- 此快照最后确认于
- 2025/12/02 01:38 3 个月前
P14360 [CSP-J 2025] 多边形 / polygon 题解
题目分析
给定 根长度为 的木棍,求有多少种选择方案使得选出的木棍能拼成多边形。
多边形条件:至少选 根木棍,且满足 。
答案对 取模。
数据范围分析
根据题目给出的测试点分布进行数据分层:
| 测试点 | 策略 | ||
|---|---|---|---|
| 1-3 | 暴力枚举 | ||
| 4-6 | 暴力枚举 | ||
| 7-10 | 暴力枚举 | ||
| 11-14 | 容斥优化 | ||
| 15-17 | 组合数学 | ||
| 18-20 | 组合数学 | ||
| 21-25 | 容斥优化 |
算法一:组合数学(特殊性质:全为 1)
适用范围: 对所有 成立
时间复杂度:
核心思想: 当所有木棍长度都是 时,选 根木棍,最大值必为 ,条件变为:
即只要选择 根木棍即可。
答案公式:
推导:
- 总方案数:
- 减去选 根:
- 减去选 根:
- 减去选 根:
代码实现:
CPPlong long qpow(long long base, long long exp) {
long long res = 1;
while (exp) {
if (exp & 1) res = res * base % MOD;
base = base * base % MOD;
exp >>= 1;
}
return res;
}
long long C(int n, int m) {
if (m > n || m < 0) return 0;
long long num = 1, den = 1;
for (int i = 0; i < m; i++) {
num = num * (n - i) % MOD;
den = den * (i + 1) % MOD;
}
return num * qpow(den, MOD - 2) % MOD;
}
// 特殊性质判断
bool allOne = true;
for (int i = 1; i <= n; i++) {
if (a[i] != 1) {
allOne = false;
break;
}
}
if (allOne) {
long long ans = qpow(2, n);
ans = (ans - 1 - n - C(n, 2) % MOD + 2LL * MOD) % MOD;
cout << ans << endl;
return;
}
算法二:暴力枚举 (pts 1-10)
适用范围:
时间复杂度:
核心思想: 直接 DFS 枚举所有子集,对每个子集判断是否满足条件。
对于每个位置可以选或不选,最终检查:
- 选出的木棍数量
代码实现:
CPPif (n <= 20) {
long long ans = 0;
function<void(int, int, int, int)> dfs = [&](int pos, int cnt, int sum, int maxval) {
if (pos == n + 1) {
if (cnt >= 3 && sum > 2 * maxval) {
ans++;
ans %= MOD;
}
return;
}
dfs(pos + 1, cnt, sum, maxval);
dfs(pos + 1, cnt + 1, sum + a[pos], max(maxval, a[pos]));
};
dfs(1, 0, 0, 0);
cout << ans << endl;
}
算法三:容斥优化 (pts 11-14, 21-25)
适用范围: 且非全
时间复杂度: ,其中
核心思想: 对于第 根作为最大值,从前 根任意选的方案数为 ,减去不合法方案数。
关键思路
枚举每根木棍作为最大值 ,需要满足:
- 从前面的木棍中选若干根
- 选出的木棍总和 满足 ,即
- 至少选 根其他木棍(加上最大值共 根)
容斥原理:
- 从前 根任意选: 种方案
- 不合法方案:选出的和
则答案为:
算法步骤
- 排序数组:方便枚举最大值
- 背包 DP:用 表示从前若干根木棍中选出和为 的方案数
- 边插入边统计:
- 处理第 根木棍时,更新 数组
- 计算 :第 个位置不合法方案数(和 )
- 累加答案:对于每个位置 ,答案加上
关键优化
为什么 统计的是和 ?
因为处理第 根时, 数组包含了前 根的所有组合。当第 根作为最大值时,不合法方案就是从前 根中选出和 的方案。
初始化:
- :不选任何木棍(代表选 根的方案)
- :只选第一根
状态转移:
对于第 根木棍:
- 更新背包:(从后往前)
- 统计不合法方案:
快速幂计算 :
使用递归 + 位运算优化,左移一位相当于乘 。
CPPint qpow2(int b) {
if (!b) return 1;
int res = qpow2(b >> 1);
res = (1ll * res * res) % MOD;
if (b & 1) (res <<= 1) %= MOD;
return res;
}
完整代码实现
CPPelse {
int f[MAXV], sum[MAXN];
memset(f, 0, sizeof(f));
memset(sum, 0, sizeof(sum));
sort(a + 1, a + n + 1);
f[a[1]] = f[0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = MAXV - 1; j >= a[i]; j--) {
(f[j] += f[j - a[i]]) %= MOD;
}
for (int j = 0; j <= a[i + 1]; j++) {
(sum[i] += f[j]) %= MOD;
}
}
int ans = 0;
for (int i = 3; i <= n; i++) {
(ans += (qpow2(i - 1) - sum[i - 1] + MOD) % MOD) %= MOD;
}
cout << ans << endl;
}
复杂度总结
| 算法 | 时间复杂度 | 空间复杂度 | 测试点 |
|---|---|---|---|
| 组合数学 | 15-20 | ||
| 暴力枚举 | 1-10 | ||
| 容斥优化 | 11-14, 21-25 |
其中 。
核心技巧总结
- 特殊性质优化:利用 的性质简化为组合数问题
- 枚举最大值:将问题转化为从剩余木棍中选择的方案数统计
- 容斥原理:总方案数 - 不合法方案数
- 背包 DP:统计选出特定和的方案数
- 边插入边统计:避免重复计算,降低时间复杂度
易错点
- 模运算:所有加法、乘法运算后都要取模
- 负数取模:减法后可能为负,需要加 再取模
- 初始化: 和 都要初始化
- 边界处理: 统计时注意不要越界
完整 AC 代码
CPP#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fclose fclose(stdin), fclose(stdout)
#define debug(x) cerr << #x << " = " << (x) << endl
const int MOD = 998244353;
const int MAXN = 5005;
const int MAXV = 5005;
int n, a[MAXN];
long long qpow(long long base, long long exp) {
long long res = 1;
while (exp) {
if (exp & 1) res = res * base % MOD;
base = base * base % MOD;
exp >>= 1;
}
return res;
}
long long C(int n, int m) {
if (m > n || m < 0) return 0;
long long num = 1, den = 1;
for (int i = 0; i < m; i++) {
num = num * (n - i) % MOD;
den = den * (i + 1) % MOD;
}
return num * qpow(den, MOD - 2) % MOD;
}
int qpow2(int b) {
if (!b) return 1;
int res = qpow2(b >> 1);
res = (1ll * res * res) % MOD;
if (b & 1) (res <<= 1) %= MOD;
return res;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
bool allOne = true;
for (int i = 1; i <= n; i++) {
if (a[i] != 1) {
allOne = false;
break;
}
}
if (allOne) {
long long ans = qpow(2, n);
ans = (ans - 1 - n - C(n, 2) % MOD + 2LL * MOD) % MOD;
cout << ans << endl;
return;
}
if (n <= 20) {
long long ans = 0;
function<void(int, int, int, int)> dfs = [&](int pos, int cnt, int sum, int maxval) {
if (pos == n + 1) {
if (cnt >= 3 && sum > 2 * maxval) {
ans++;
ans %= MOD;
}
return;
}
dfs(pos + 1, cnt, sum, maxval);
dfs(pos + 1, cnt + 1, sum + a[pos], max(maxval, a[pos]));
};
dfs(1, 0, 0, 0);
cout << ans << endl;
} else {
int f[MAXV], sum[MAXN];
memset(f, 0, sizeof(f));
memset(sum, 0, sizeof(sum));
sort(a + 1, a + n + 1);
f[a[1]] = f[0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = MAXV - 1; j >= a[i]; j--) {
(f[j] += f[j - a[i]]) %= MOD;
}
for (int j = 0; j <= a[i + 1]; j++) {
(sum[i] += f[j]) %= MOD;
}
}
int ans = 0;
for (int i = 3; i <= n; i++) {
(ans += (qpow2(i - 1) - sum[i - 1] + MOD) % MOD) %= MOD;
}
cout << ans << endl;
}
}
signed main() {
//file(polygon);
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
//cin >> T;
while (T--) {
solve();
}
//fclose;
return 0;
}
总结
本题通过数据分层实现不同算法:
- 特殊性质(全为 1)用组合数学
- 小数据用暴力
- 大数据用容斥优化
核心是理解:枚举最大值后,问题转化为从剩余木棍中选择使得和满足条件的方案数统计问题,使用容斥原理 = 总方案数 - 不合法方案数。
温馨提示
注意事项:
- 比赛的时候一定要检查有没有加 freopen, 如果没加的话就加上,加上了之后写代码时先注释掉,最后取消注释的时候一定要再编译一遍!(我同学已经因为这个吃亏了)
- 比赛写到最后1小时的时候,如果别的题没有思路,就先给已经写过的题做数据分层,根据不同范围分析暴力枚举,深度优先搜索(Depth-First Search, DFS)等算法
解题技巧
- 可以根据数据范围来猜测算法 时间/空间 复杂度 然后再根据复杂度选取合适的算法
- 要多观察特殊性质,有时可以通过特殊性质推导出正解,也可以通过特殊性质进行混分,比赛总是分越多拿奖概率越高
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...