CF1943D2 Counting Is Fun (Hard Version)
传送门
性质1:规定\(a_0=a_{n+1}=0\),则满足条件的序列一定有\(a_i≤a_{i-1}+a_{i+1}\)
利用这个性质,我们可以设\(dp_{i,j,k}\)表示填充到第\(i\)位,第\(i-1\)位填\(j\),第\(i\)位填\(k\)的方案数,转移即
\(dp_{i,j,k}=\sum_{z=max(0,j-k)}^{lim}dp_{i-1,z,j}\)
利用前缀和优化复杂度\(O(nk^2)\),可以通过Easy Version
性质2:任意两个不合法的位置不会相邻出现,即$ \forall i$ 满足\(a_i>a_{i-1}+a_{i+1}\),则\(a_{i+1}≤a_{i}+a_{i+2}\)。这是显然的
利用这个性质就可以设\(dp_{i,j}\)表示第\(i\)位填\(j\)的合法方案数,转移可以利用简单容斥,即
\(dp_{i,j}=\sum dp_{i-1} - \sum_{x=0}^{lim-j-1} dp_{i-2,x}(lim-j-x)\)
后面一项指的是,当前位置选\(j\)而\(i-1\)位置任选的情况下,第\(i-2\)位置如果选择\(x\)可能导致不合法,而不合法的情况共有\(lim-j-x\)种(因为当前选\(j\),第\(i-2\)位选\(x\),则第\(i-1\)位选\(x+j,x+j+1,...,lim\)都会导致不合法),将这部分不合法的减去即可
而想要维护后面一项只需要倒序枚举\(j\),可以看每个\(dp_{i-2,x}\)贡献次数维护
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
const int inf = 1e18 + 10;
int n,k,mod;
void solve() {
cin >> n >> k >> mod;
vector<vector<int> > dp(n + 10,vector<int>(k + 10));
vector<vector<int> > pre1(n + 10,vector<int>(k + 10));
dp[0][0] = 1;
for(int i = 0;i <= k;i++) {
pre1[0][i] = 1;
dp[1][i] = 1;
pre1[1][i] = i + 1;
}
for(int i = 2;i <= n + 1;i++) {
int s = 0;
for(int j = k;j >= 0;j--) {
s = (s + (k - j - 1 >= 0 ? pre1[i - 2][k - j - 1] : 0)) % mod;
dp[i][j] = (pre1[i - 1][k] - s + mod) % mod;
}
pre1[i][0] = dp[i][0];
for(int j = 1;j <= k;j++) pre1[i][j] = (pre1[i][j - 1] + dp[i][j]) % mod;
}
cout << dp[n + 1][0] << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int t = 1;
cin >> t;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号