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