Atcoder BC 207 E

题目大意
给你一个序列A,要求你把这个序列分为 k 份(从1到k排序),对于每一份 ki 其内部元素和模 ki 应当为 0,试问可以找出多少种分法?
解题思路

  1. n3 暴力DP
dp[j][i]:对于前i个数,分成j个区间的方法总数
for (int i = 1; i <= n;i++){
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
        dp[1][i] = 1;
    }
    for (int k = 2; k <= n;k++){//区间长度从2开始枚举
        for (int i = 1; i < n; i++){//枚举前k-1个区间放进去了多少数
            for (int j = n; j > i; j--){//判断那些数可以分到第k个区间
                if((sum[j] - sum[i]) % (k) == 0){//更新方案数
                    (dp[k][j] += dp[k - 1][i]) %= mod;
                }
            }
        }
    }

虽然保证正确,但是时间复杂度过高,所以对判断可以分进第k个区间的步骤进行优化,由于两个数字的差模某一个数为0等价于这两个数字模这个数的值相同,所以可以用前缀和预处理模完某个数的结果,代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
const int N = 1e5 + 100;
const int mod = 1e9 + 7;
ll a[3010];
ll sum[3010];
ll dp[3010][3010], presum[3010][3010];
int main(){
    int n;
    cin >> n;
    for (int i = 1; i <= n;i++){
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
        dp[1][i] = 1;
    }
    for (int i = 1; i <= n;i++){
        for (int j = 2; j <= i;j++){
            ll tag = sum[i] % j;//计算取模的结果
            dp[j][i] = presum[j][tag];//方案数即为分为j个区间且模数为tag的数目
        }
        for (int j = 1; j <= n;j++){
            ll tag = sum[i] % j;//计算前i个数的和模1~n的所有结果
            presum[j][tag] += dp[j - 1][i];//更新分为j个区间且模数为tag的值
            presum[j][tag] %= mod;
        }
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++){
        ans += dp[i][n];
        ans %= mod;
    }
    cout << ans << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值