蒟蒻の线性dp学习总结
线性dp
定义
线性动态规划,是较常见的一类动态规划问题,其是在线性结构上进行状态转移,这类问题不像背包问题、区间DP等有固定的模板。
线性动态规划的目标函数为特定变量的线性函数,约束是这些变量的线性不等式或等式,目的是求目标函数的最大值或最小值。
因此,除了少量问题(如: LIS、 LCS、 LCIS等)有固定的模板外,大部分都要根据实际问题来推导得出答案。
模板
记忆化深搜版
#include<bits/stdc++.h>
using namespace std;
int n,m,a[110];
int mem[210][210];//记忆化数组
int f(int x,int y){
//mem数组剪枝部分
if(mem[x][y]!=-1){
return mem[x][y];
}
if(y==0){
return mem[x][y]=1;
}
if(x==0){
return mem[x][y]=0;
}
//枚举计算部分
int ans=0;
for(int i=0;i<=a[x]&&i<=y;i++){
ans+=f(x-1,y-i)%1000007;
}
return mem[x][y]=ans%1000007;
}
int main(){
cin>>n>>m;
//mem数组初始化
memset(mem,-1,sizeof(mem));
for(int i=1;i<=n;i++){
cin>>a[i];
}
cout<<f(n,m)%1000007;
return 0;
}
状态方程版
#include<bits/stdc++.h>
using namespace std;
int n,m;
int dp[40][40];//状态数组
int main(){
cin>>n>>m;
dp[0][1]=1;//初始化
//转移方程部分
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(j==1){
dp[i][j]=dp[i-1][j+1]+dp[i-1][n];
}else if(j==n){
dp[i][j]=dp[i-1][1]+dp[i-1][j-1];
}else{
dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
}
}
}
cout<<dp[m][1];
return 0;
}
例题
P1057 传球游戏
思路
我们可以发现,任何一个位置都只能从左边和右边传过来,那么他只能从他左边和他右边的同学手上接到球,那球传到他手上的路径数是不是球传到他左边同学的路径数与球传到他右边同学的路径数之和
在初始情况下,小蛮手中必然有且只有一个球,记为1;
第一轮传球后,小蛮必然将手中的球传给2号或5号同学,于是这两个同学各有1种方式接到球;
第二轮传球后,(如果上一轮小蛮将球传给2号)2号同学必然将球传给小蛮或3号,(如果上一轮小蛮将球传给5号),5号同学必然将球传给小蛮或4号,于是小蛮有2种情况接到球(分别从2号和5号手中);
第三轮及其后以此类推。
只需特判一下1和n即可
C o d e Code Code
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[40][40];
/*
dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
j=1:dp[i][j]=dp[i-1][j+1]+dp[i-1][n];
j=n:dp[i][j]=dp[i-1][1]+dp[i-1][j-1];
*/
int main(){
cin>>n>>m;
dp[0][1]=1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(j==1){
dp[i][j]=dp[i-1][j+1]+dp[i-1][n];
}else if(j==n){
dp[i][j]=dp[i-1][1]+dp[i-1][j-1];
}else{
dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
}
// cout<<dp[i][j]<<" ";
}
// cout<<endl;
}
cout<<dp[m][1];
return 0;
}
P1077 摆花
思路
从 1 到 n n n 考虑每个 x i x_i xi 的值,和当前前 i i i 个数的总和 k k k,然后枚举当前 x i x_i xi 所有可能的值,再递归求解。
C o d e Code Code
#include<bits/stdc++.h>
using namespace std;
int n,m,a[110];
int mem[210][210];
int f(int x,int y){
if(mem[x][y]!=-1){
return mem[x][y];
}
if(y==0){
return mem[x][y]=1;
}
if(x==0){
return mem[x][y]=0;
}
int ans=0;
for(int i=0;i<=a[x]&&i<=y;i++){
ans+=f(x-1,y-i)%1000007;
}
return mem[x][y]=ans%1000007;
}
int main(){
cin>>n>>m;
memset(mem,-1,sizeof(mem));
for(int i=1;i<=n;i++){
cin>>a[i];
}
cout<<f(n,m)%1000007;
return 0;
}
P1115 最大子段和
思路
一点一点往后递推加,找最大值
C o d e Code Code
模拟求解(最为easy的办法)
#include<bits/stdc++.h>
using namespace std;
int n,a[200100],t,ans;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
ans=a[1];
t=a[1];
for(int i=2;i<=n;i++){
if(t<0)t=0;
t+=a[i];
ans=max(ans,t);
}
cout<<ans;
return 0;
}
dp求解
#include<bits/stdc++.h>
using namespace std;
int a[200001],n,ans[200001]={0};
int sum=-9999999;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
ans[i]=max(ans[i-1]+a[i],a[i]);
sum=max(sum,ans[i]);
}
cout<<sum;
return 0;
}