洛谷P8848 [JRKSJ R5] 1-1 B 组合+dp

题面

https://www.luogu.com.cn/problem/P8848

分析

比赛时打了个暴力就跑了,事后看讨论帖分析出来了正解。

1-1A的结论就是,最大子段和一定是 ai。因为最优数组排列一定是1与-1交叉排列。

首先容易发现,如果数组中1的个数比1出现的多(或者相等),那么无论有多少1,对最大子段和是没有贡献的。

1的个数为tot0 , 1的个数为tot1。这种情况下,问题转化为在tot0个数之间插tot1个板,很显然的插板法,答案为Ctot0+1tot1

对于第二种情况,即1的个数比1出现的少,考虑动态规划。

dp[i][j]为前i个数且和为j的方案数。易得转移方程为:

dp[i][j]=dp[i1][j1]+dp[i1][j+1]

答案即为dp[n][ai]

但是1e4的数据会被卡MLE(比如我),考虑滚动数组优化,代码参考@此处

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;
}
posted @   SxtoxA  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
12 13
点击右上角即可分享
微信分享提示