洛谷P8848 [JRKSJ R5] 1-1 B 组合+dp
题面
https://www.luogu.com.cn/problem/P8848
分析
比赛时打了个暴力就跑了,事后看讨论帖分析出来了正解。
1-1A的结论就是,最大子段和一定是 。因为最优数组排列一定是1与-1交叉排列。
首先容易发现,如果数组中的个数比出现的多(或者相等),那么无论有多少,对最大子段和是没有贡献的。
设的个数为 , 的个数为。这种情况下,问题转化为在tot0个数之间插tot1个板,很显然的插板法,答案为。
对于第二种情况,即的个数比出现的少,考虑动态规划。
令为前个数且和为的方案数。易得转移方程为:
答案即为。
但是的数据会被卡(比如我),考虑滚动数组优化,代码参考@此处
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
const int maxn = 1e4 + 10;
const int mod = 998244353;
int n, a[maxn], sum;
ll dp[maxn], dp2[maxn];
ll fac[maxn];
int tot, tot2;
ll qpow(ll a, ll b) {
ll ans = 1LL, base = a;
while (b) {
if (b & 1) ans = (ans * base) % mod;
base = (base * base) % mod;
b >>= 1;
}
return ans;
}
ll C(ll n, ll k) {
if (k > n) return 0;
return (fac[n] * qpow(fac[k], mod - 2) % mod) * qpow(fac[n - k], mod - 2) % mod;
}
ll Lucas(ll n, ll k) {
if (!k) return 1LL;
return C(n % mod, k % mod) * Lucas(n / mod, k / mod) % mod;
}
int main() {
fac[0] = 1LL;
scanf("%d", &n);
for (int i = 1; i <= n + 1; i++) fac[i] = (fac[i - 1] * i) % mod;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if (a[i] == -1) tot++;
else tot2++;
sum += a[i];
}
if (tot >= tot2) {
printf("%lld", Lucas(tot + 1, tot2));
} else {
dp2[0] = 1;
for (int i = 1; i <= n; i++) {
dp[0] = dp2[1];
for (int j = 1; j <= sum; j++)
dp[j] = (dp2[j - 1] + dp2[j + 1]) % mod;
for (int j = 0; j <= sum; j++)
dp2[j] = dp[j];
}
printf("%lld", dp[sum]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!