「CF1943D2」Counting Is Fun (Hard Version)
我们先来等价转换一下合法序列的特征,我们发现,对于任意的\(i,a_i > a_{i -1} + a_{i + 1}\),那么是好的。
必要性是显然的,主要是充分性如何证明,考虑对差分数组求解
那么就是说,对于个一个数列,你每一次需要选择不相邻的两个数同加减,是否能全部变为零,相对而言,前面的优势更大,所以一个贪心的想法是对于每一个正数从前往后的与之后的负数依次抵消。这样的贪心与我们的限制条件是否能够造出一组解呢?
转换一下条件,也即$0 \le a[i - 1] + (a[i + 1] - a[i]) $ 也就是 $ 0 \le (\sum \limits_{1 \le j \le i - 1} d_j) + d_{i + 1} $ ,感性地说,这说明每一个负数之前可用的的正数与负数抵消后都是够用的,按照这个贪心算法即可构造出来一个序列,也即充分性。
基于此,我们有一个基础dp_{i , j , k},表示\(i - 1\)位置 填\(j\), \(i\)位置填\(k\)的方案数,前缀和优化转移即可\(O(n^3)\)解决问题,这也是 easy vision的做法。
下面是个人觉得最神仙的地方,我们继续发掘性质,一个位置不合法也即\(a[i] > a[i - 1] + a[i + 1]\),你会发现,不可能有两个相邻的位置同时不合法,首先缩减dp状态,\(dp_{i , j}\) 表示第\(i\)个位置填了\(j\),前\(i - 1\)已经合法的方案数,我们来填\(i + 1\), 如果\(i + 1\)不合法,那么\(i ,i + 2\)一定合法,我们即可通过\(i\)转移出\(i + 2\)的\(i + 1\)不合法方案,只需算出\(i + 1\)所有方案数即可容斥出合法的方案
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 3003;
int t , n , k , p;
int dp[Maxn][Maxn] , s[Maxn][Maxn] , ss[Maxn][Maxn];
int main() {
cin >> t;
while(t--) {
cin >> n >> k >> p;
if(n == 1) {
cout << 1 << '\n';
continue;
}
if(n == 2) {
cout << k + 1 << '\n';
continue;
}
const int Mod = p;
for(int i = 0 ; i <= k ; i++) {
dp[1][i] = 1;
dp[2][i] = i + 1;
s[1][i] = dp[1][i];
s[2][i] = dp[2][i];
if(i) s[1][i] = (s[1][i - 1] + s[1][i]) % Mod , s[2][i] = (s[2][i - 1] + s[2][i]) % Mod;
ss[1][i] = s[1][i];
ss[2][i] = s[2][i];
if(i) ss[1][i] = (ss[1][i - 1] + ss[1][i]) % Mod , ss[2][i] = (ss[2][i - 1] + ss[2][i]) % Mod;
}
for(int i = 3 ; i <= n + 1 ; i++) {
int tmp = 0;
for(int j = 0 ; j <= k ; j++) {
tmp = (tmp + dp[i - 1][j]) % Mod;
}
for(int j = 0 ; j <= k ; j++) {
dp[i][j] = (Mod + tmp - ((k ^ j) ? ss[i - 2][k - j - 1] : 0)) % Mod;
// printf("(%d %d)[%d %d]\n", i , j , dp[i][j] , ((k ^ j) ? ss[i - 2][k - j - 1] : 0));
}
s[i][0] = ss[i][0] = dp[i][0];
for(int j = 1 ; j <= k ; j++) {
s[i][j] = (s[i][j - 1] + dp[i][j]) % Mod;
ss[i][j] = (ss[i][j - 1] + s[i][j]) % Mod;
}
}
cout << dp[n + 1][0] << '\n';
}
}~~~