CF1485F
题目链接
点我跳转
题目大意
给定一个长度为
N 的序列bi 问有多少个长度为
N 的序列a 使得
b[i] = a[i] 或
b[i] = ∑a[j] , j∈[1,i]
解题思路
定义
dp[i][j] 表示前i 项的前缀和为j 的序列a 的个数,其中dp[0][0] = 1 ( 因为前缀和很大,所以需要用
map 来操作 )那么:
当
b[i] = a[i] 时 ,dp[i][j] = dp[i - 1][j - b[i]] 当
b[i] = ∑a[j] , j∈[1,i] 时 ,dp[i][b[i]] = ∑dp[i - 1][j],j∈[-inf,inf] 对于第一种转移相当于把整个数组的值向右平移
b[i] 对于第二种转移需要注意当
sum[i-1] = 0 时,b[i] 既等于a[i] 又等于∑a[j] , j∈[1,i] 相当于多转移了一次
dp[i - 1][0] ,所以需要减去dp[i - 1][0] 最后的答案
ans = ∑dp[n][j],j∈[-inf,inf] ,复杂度为N^2logN (log 的复杂度源于map )考虑优化:
定义
sum[i] 表示长度为i 且满足题目条件的序列a 的个数对于第一种转移,只是把数值向右平移,并不会导致答案的个数增加,所以
sum[i] = sum[i - 1] 对于第二种转移,
dp[i][b[i]] += sum[i - 1] , 同时减去dp[i - 1][0] ,相当于sum[i] += sum[i - 1] , sum[i] -= dp[i - 1][0] 于是我们可以定义
py 表示平移的长度,起初py = 0 ,每计算完sum[i] 后 , 令py += b[i] 那么
dp[i - 1][j] 则可以用dp[j - py] 表示而
dp[i][j] 则可以用dp[j - py - b[i]] 表示于是可得
sum[i] += sum[i - 1] - dp[0 - py] ,dp[b[i] - py - b[i]] += sum[i - ] - dp[0 - py] 最后的答案
ans = sum[n] , 复杂度为NlogN
AC_Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e5 + 10 , mod = 1e9 + 7;
map<int , int>dp;
int b[N];
signed main()
{
int T = 1;
cin >> T;
while(T --)
{
dp.clear();
int n , sum = 1 , py = 0;
cin >> n;
for(int i = 1 ; i <= n ; i ++) cin >> b[i];
dp[0] = 1;
for(int i = 1 ; i <= n ; i ++)
{
int add = (sum - dp[0 - py] + mod) % mod;
sum = (sum + add) % mod , py += b[i];
dp[b[i] - py] = (dp[b[i] - py] + add) % mod;
}
cout << sum << '\n';
}
return 0;
}