基础线性Dp (练习笔记)(25.10.31)

基础线性Dp (练习笔记)

尖头的金牌

  1. 该题可以通过推导式子,得到前一项加的金牌是不得超过后一项加的金牌的

    \(a'[i]=a[i]+x[i]>a'[i+1]=a[i+1]+x[i+1]\) 通过式子表达出在加上某些数后出现前一个比后一个大的情况
    然后我们可以去设c为a前后差值:\(c=a[i+1]-a[i]<x[i]-x[i+1]\) (在c比两个加的差值小的时候)
    随后去列出一个相同的情况,发现:\(a'[i+1]=a[i+1]+x[i]-c>a'[i]=a[i]+x[i+1]+c\) 与前一个式子相同 所以我们在化简后,只能去考虑 \(x[i]-x[i+1] \le c\) 的方案(因为\(x[i+1] \ge 0\))所以只考虑 \(x[i] \le c\)

  2. 然后我们尝试将i设为阶段,状态设计j为总奖牌数,本盒子里新放的为k

    \[f[i][j][k]=\sum_{K=0}^{K \le (a[i]+k)-a[i-1]}{f[i-1][j-k][K]} \]

  3. 求和式还可以使用前缀和优化,并且 \(K \le j-k\)

  4. 代码:

    const int mod=998244353;
    const int N=300;
    int a[N+1],f[N+1][N+1][N+1];//第i个盒子放k个,共j个 
    int main(){
    	int n,m,i,j,k,x;
    	scanf("%d%d",&n,&m);
    	for(i=0;i<=n;i++)f[1][i][i]=1;
    	for(i=1;i<=n;i++)scanf("%d",&a[i]);
    	sort(a+1,a+n+1);
    	for(i=2;i<=n;i++){
    		for(j=0;j<=m;j++){
    			for(k=0;k<=j;k++){
    				x=min(a[i]+k-a[i-1],j-k);//k能到达的最大值和j-k看一下哪个小,就使用哪个
    				//因为两边可使用的数量可能是一样的,但是还要满足不能超过j的总和
    				(f[i][j][k]+=f[i-1][j-k][x])%=mod;
                   	 //先去基础转移,单纯表示在i,j下k个能有多少方案
    			}
                //这里是前缀和优化,直接累加每一个自己,表示的是在i,j下从0到k总共有多少方案
    			for(k=1;k<=j;k++)(f[i][j][k]+=f[i][j][k-1])%=mod;
    		}
    	}
    }
    
  5. 总结:

    在遇见带约束的计数问题的时候同时可以考虑dp计数,首先要分析约束是哪些,状态必须可以推导约束,所以分为:阶段(i),状态变量(j,k),决策(可能需要有不同的转移)

    本质也是把全局约束+逐步解决问题拆解成相邻阶段局部约束(数学归纳法推导),所以状态变量是要用于处理约束的,阶段是用于分层解决问题的

P2758 编辑距离

如果要搜索,会根据A进行逐步搜索,与B对应,然后去看三个操作(分别与B对应)一直到最后,去求最小答案,之后就可以发现子问题是A在某个长度做完操作变到B的某个长度(如果进行优化,那我们需要的是松弛后的最小值),所以:
可以设置\(dp_{i,j}\)状态表示在A长度为i的时候,变为B的长度为j的最少操作次数是多少,然后进行枚举并且与四个决策相关联进行转移

P8816 上升点列

首先考虑从搜索转移,搜索是从每个点去做枚举,去看下一个点能不能选,然后记录着当前长度,使用k的点数,两个点之间如果转换,那么就需要距离-1和剩下的K值相同,而多出来的k值就直接加上(表示为在第i点,之前用了j个新点,然后跳转到新点)(松弛需要最大值,所以需要把多余用的点加在距离上)

所以设\(dp_{i,j}\)状态表示:在第i个点结尾的时候,使用了j个新点的最长长度

P7074 方格取数

该题先从搜索考虑,发现可以记忆化搜索,使用转移方向作为记忆数组

然后还可以使用Dp,这里讲一下Dp
Dp,首先设置状态x,y然后再设置一个方向,就可以使用转移
通过观察,(因为需要思考一个Dp数组是由哪些子问题而来),所以发现一个Dp数组是从左,上,下而来,所以先去处理左,上下,不断回退,可以抽象理解为先处理完一列再处理下一列,之后去详细写转移式就行

Dp关于无后效性的证明,主要去证明一个数组不会被其后面的数组改变就行,然后题目满足不重复,只向右,所以一定是无后效性的

P2516 最长公共子序列

首先第一问,LCS,因为搜索中需要的是逐步枚举,所以涉及状态为逐步状态,然后转移也只需要从上一个去转移写就行
\(dp_{i,j} = dp_{i-1,j-1}+1\) \(dp_{i,j} = max(dp_{i-1,j},dp_{i,j-1})\)
式子可行性在于,可以有明确的转移,有最优子结构(因为在一个新的字母加入去做枚举的时候,便需要用到前面的最优子问题),无后效性,当前最佳与后面无关

第二问,求LCS个数,这里的想法是,逐步枚举,并且绑定在每一步LCS长度与个数,想要达成转移,有以下几点:

  1. 若有新的字母加进来,便去把上一个的LCS个数转移过来
  2. 若无新的字母(表示从 \(f[i-1][j]\)\(f[i][j-1]\) 转移来),就把这两个的数去转移过来
  3. 特殊情况是两个都被转移,但是问题是可能会有部分重合,这里就要考虑容斥
  4. 所以如果两个都能转移,就要消除一个重合部分,就是\(f[i-1][j-1]\)
posted @ 2025-10-31 15:07  Yuriha  阅读(0)  评论(0)    收藏  举报