本文介绍一种dp的设计状态(后缀) 处理问题 一个序列中 “任意一段区间 “满足某个性质,这个时候应该想到 “ 后缀dp”
题意 给定长度为 n 的数列 ,要求每个数都在 [ − m , m ] 范围,且任意长度大于等于 2 的区间和都大于等于 0 ,问方案数。
1 ≤ n , m ≤ 5e3
思路
后缀dp秒杀,设dp[i][j]表示第i个数字选完后,所有后缀中最小值是j的合法方案数目(j可以是负数 )
如果以i为结尾时,所有后缀最小值满足>=0,那么以i为结尾的方案一定是合法方案
计算dp[i][j]的时候,利用的i-1层的信息,假设i-1层是正确的,那么第i层肯定是正确的,那么后面都正确。
遍历完所有的i,说明一个很重要的信息:以i为结尾的任意 后缀都满足“任意长度>=2的区间满足sum>=0”,那么任意一段长度大于等于2的区间都满足!!
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N=1e5 +5 ; typedef long long ll; const ll mol=998244353 ;const int P=5001 ; ll a[N],n,m; ll dp[2 ][N]; ll dpsum[2 ][N];int main () { cin>>n>>m; for (int j=-m;j<=m;j++) dp[1 &1 ][j+P]=1 ,dpsum[1 &1 ][j+P]=(dpsum[1 &1 ][j-1 +P]+dp[1 &1 ][j+P])%mol; for (int i=2 ;i<=n;i++){ for (int j=-m;j<=m;j++){ if (j>=0 ){ dp[i&1 ][j+P]=dpsum[(i-1 )&1 ][min (m,j+m)+P]-dpsum[(i-1 )&1 ][j-m-1 +P]; dp[i&1 ][j+P]=(dp[i&1 ][j+P]%mol+mol)%mol; } else { dp[i&1 ][j+P]=dpsum[i-1 &1 ][min (-j+m,m)+P]-dpsum[(i-1 )&1 ][-j-1 +P]; dp[i&1 ][j+P]=(dp[i&1 ][j+P]%mol+mol)%mol; } } for (int j=-m;j<=m;j++){ dpsum[i&1 ][j+P]=(dpsum[i&1 ][j-1 +P]+dp[i&1 ][j+P])%mol; } } cout<<dpsum[n&1 ][m+P]; }
注意
当前层j>=0,上一层可以是负数(这个时候本位置一定选正数,可以满足题意),但是j<0时,上一层必须是正数,而且 k>=-j,否则,此时一定存在某个以i为结尾的后缀(len>=2),使得这个后缀的sum<0,不符合题意
边界问题 :为什么当前层为j(j>=0)的时候,上一层最大从k=m转移而不是 k=m+j呢? 因为以i结尾的任意一个后缀的最小值不可能超过m
题意: 给定n,m,k。 n,m表示有n个男孩和m个女孩,对于任意连续的一段,男孩与女孩的数目之差不超过k,求方案数。n , m ≤ 150,k ≤ 20
思路
设dp[i][j][w][t]表示选i个男孩,j个女孩,且此时任意后缀中男孩比女孩最多多w个,女孩比男孩最多多t个的方案数目
显然,增加一个男孩或者增加一个女孩,如果上一个状态合法,那此轮状态也一定合法。
具体转移见代码
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <bits/stdc++.h> using namespace std;typedef long long ll;const int N=152 ;const int M=22 ;const ll mol=12345678 ; ll dp[N][N][M][M];int n,m,k;int main () { cin>>n>>m>>k; dp[0 ][0 ][0 ][0 ]=1 ; for (int i=0 ;i<=n;i++){ for (int j=0 ;j<=m;j++){ for (int w=0 ;w<=k;w++){ for (int t=0 ;t<=k;t++){ if (dp[i][j][w][t]){ ll tem=dp[i][j][w][t]; (dp[i+1 ][j][w+1 ][max (0 ,t-1 )]+=tem)%=mol; (dp[i][j+1 ][max (0 ,w-1 )][t+1 ]+=tem)%=mol; } } } } } ll ans=0 ; for (int w=0 ;w<=k;w++){ for (int t=0 ;t<=k;t++){ ans+=dp[n][m][w][t]; ans%=mol; } } cout<<ans<<endl; }
注意
是由当前层转移到下一层,并不是由前一层转移到当前层 ,原因如下:
1.若由前一层转移到当前层,那么其中肯定有这样的转移方程:dp[i][j][w][t]+=dp[i-1][j][max(0,w-1)][t+1],这是错的,如何确定选(i-1,j)这个状态下,女生一定比男生多t+1个呢??如果j为0呢?那是不是可以不需要多t+1个,多t个好像也可以,此时t为0。
2.理论上从前一层转移到当前层也是可以的,只不过需要特判??这个作者没有严格证明,可以自己尝试
题意: 给定n,m,p,(n,m<=400)。表示长度为n的数组,每个元素大小不超过m。
现在你可以进行如下操作任意次数:选取一个区间[l,r](r>l),使得a[l]~a[r]所有数字减1,问最终可以通过这种操作,使得整个数组变为0的 数组 有多少个,对p取模。
结论 如果数组每个元素满足a[i-1]+a[i+1]>=a[i],这样的数组是满足条件的。
做法 设·dp[i][j][k]为第i-1位置填写j,第i位置填写k的方案数。
很明显,第i维可以去更新第i+1维,即dp[i][j][k]贡献于dp[i+1][k][t],其中t满足(t>=k-j),也就是说,第i维的dp值可以去更新i+1维的一个后缀 。答案就是dp[n+1][t][0] (0<=t<=m)
我们可以借助差分的思路,dp[i][j][k] 只更新dp[i+1][k][t]即可,等到下次循环(i)的时候,先 做一遍前缀和,然后再去更新别人。
为什么不在更新完i+1后,对i+1维度做前缀和呢?
如果这样做,最开始循环的时候,dp[i][][]并没有做前缀和,我们不可以仅仅初始化dp[1][0][0]=1,想想一下,更新dp[2][][]的时候,倒数第一位必须填写0,这肯定是不对的,所以在更新i+1维度之前做前缀和可以解决这个问题。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <bits/stdc++.h> using namespace std;const int N = 405 ;const int INF = 1e9 ;typedef long long ll; ll n, m; ll p; ll dp[N][N][N];void solve () { cin >> n >> m >> p; for (int i = 1 ; i <= n+1 ; i++) { for (int j = 0 ; j <= m; j++) { for (int k = 0 ; k <= m; k++) { dp[i][j][k] = 0 ; } } } dp[1 ][0 ][0 ] = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= m; j++) { for (int k = 0 ; k <= m; k++) { dp[i][j][k] += dp[i][j][k-1 ]; dp[i][j][k] %= p; } } for (int j = 0 ; j <= m; j++) { for (int k = 0 ; k <= m; k++) { dp[i + 1 ][k][max (0 , k - j)] += dp[i][j][k]; dp[i + 1 ][k][(max (0 , k - j))] %= p; } } } ll ans = 0 ; for (int j = 0 ; j <= m; j++) { ans += dp[n + 1 ][j][0 ]; ans %= p; } cout << ans << endl; }int main () { int t = 1 ; cin >> t; while (t--) solve (); return 0 ; }
总结 通过上面的题,发现,任意一段需要满足某一性质,则需要后缀dp。
dp定义:在某个状态下,任意后缀的最大属性(或者最小属性)为k 的方案数
注意
一定是在某个状态下
任意后缀
最大(最小)属性为k