闫氏dp分析法
一、162:Post Office
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
const int N=305;
int a[N];
int dp[N][35];//前i个村庄建了j个邮局,i个村庄到邮局的最小距离和
//假设第j个邮局建在k村庄(j<=k<=i),即前j-1个邮局建在1~k-1
//最后一个邮局即第j个建在k~i之间,自然是建在中间才使距离和最小
//在[k,i]之间建一个邮局(建在(k+i)/2),s[k][i]=Sum(a[j]-a[(k+i)/2])
int s[N][N];
int main(int argc, char** argv) {
int n,m;
cin>>n>>m;//邮局建在某几个村庄
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
for(int k=i;k<=j;k++)
s[i][j]+=abs(a[k]-a[(i+j)/2]);
}
}
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++)dp[i][1]=s[1][i];
for(int i=2;i<=n;i++){
for(int j=1;j<=m&&j<=i;j++){//邮局数肯定小于等于村庄数
for(int k=j-1;k<i;k++)
dp[i][j]=min(dp[i][j],dp[k][j-1]+s[k+1][i]);
}
}
cout<<dp[n][m];
return 0;
}
//好像突然懂了什么,动态规划先假设已知状态dp[i][j]
//再对该已知状态区间的右边界即最后一个节点,进行讨论
二、4378. 选取数对
首先想贪心,贪心不成就是dp
枚举位置(数组的下标) 需要一维
其余的,每多一个限制就要多上一维
选k个区间是个限制
状态表示的属性无非就是最大值最小值和数量
状态计算 对应集合的划分,
每个状态是个集合,化整为零,把每个集合划分成若干个子集,逐个击破取最大值
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
//typedef long long ll;
#define int long long
const int N=5010;
int a[N];
int s[N];//前缀和
int dp[N][N];// 前i个数中选了j区间 得到的最大sum值
//怎么得到的呢?试想dp[x][j-1](m*(j-1)<=x<=i-m)已知,
//那最后一个区间怎么取呢? 自然是取[x+1,x+m]区间上的最大值
signed main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]+a[i];
}
memset(dp,-0x3f,sizeof(dp));
for(int i=0;i<=n;i++)dp[i][0]=0;
//写法一: 时间复杂度为O(n^3),超时
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
dp[i][j]=dp[i-1][j];
for(int x=m*(j-1);x<=i-m;x++)
dp[i][j]=max(dp[i][j],dp[x][j-1]+s[x+m]-s[x]);
}
}
//或许将第三重循环写成以下两行形式 会更容易优化?
// for(int x=m*j;x<=i;x++)
// dp[i][j]=max(dp[i][j],dp[x-m][j-1]+s[x]-s[x-m]);
还是不理解写法二的优化,
//写法二:将时间复杂度从O(n^3)优化为为O(n^2)
for(int j=1;j<=k;j++){
int maxx=0;
for(int i=m*j;i<=n;i++){
// dp[i][j]=dp[i-1][j];
maxx=max(maxx,dp[i-m][j-1]+s[i]-s[i-m]));
dp[i][j]=maxx;
}
}
//写法三:正宗dp,是我没有将状态划分为子集之前的写法
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
dp[i][j]=dp[i-1][j];
if(i-m+1>=1)
dp[i][j]=max(dp[i][j],dp[i-m][j-1]+s[i]-s[i-m]);
}
}
cout<<dp[n][k];
return 0;
}
//写法四:背包问题
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
// typedef long long ll;
#define int long long
const int N=5010;
int a[N];
int s[N];
int dp[N][N];//cnt个物品里选k件,最大价值
signed main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int cnt=n-m+1;
for(int i=1;i<=cnt;i++){
for(int j=1;j<=m;j++)
s[i]+=a[i+j-1];
}
fill(dp[0],dp[0]+N*N,0xc0c0c0c0);//这种方式对于初始化longlong为无穷小是要出错的,逐字节的ok
// memset(dp,0xc0,sizeof(dp));
// dp[0][0]=0;
for(int i=0;i<=cnt;i++)dp[i][0]=0;
// dp[1][1]=s[1];
// dp[2][1]=s[2];
for(int i=0;i<=m;i++)dp[i][1]=s[i];
for(int i=1;i<=cnt;i++){
for(int j=1;j<=k;j++){
// if(i<m)dp[i][j]=s[i];//选的两个物品必须要相隔>=m
// 太久没做dp了,怎么能是以上的形式呢
// i<m,那么前i件物品只能取一个,应该是dp[i][j]=max(dp[i-1][j],s[i]);前者是第i件不取,后者是取第i件
if(i<m)dp[i][j]=max(dp[i-1][j],s[i]);
else{//i>=m,那么第i件可以取也可以不取
//dp[i][j]=max(dp[i][j],dp[i-m][j]);
dp[i][j]=max(dp[i-1][j],dp[i-m][j-1]+s[i]);//前者不取,后者取了,获得的总价值是从取第i-m件物品推过来的,即,取了第i件,那么上一件只能是在i-m之前的物品
}
}
}
cout<<dp[cnt][k];
return 0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
dp[i][j]=dp[i-1][j];
for(int x=m*(j-1);x<=i-m;x++)
dp[i][j]=max(dp[i][j],dp[x][j-1]+s[x+m]-s[x]);
当j固定的时候,起点固定,从小到大枚举i
右端点逐步向右走,整个要求最大值的集合是逐步扩大的
求动态集合的最大值,只需要存储当前集合的最大值
不需要枚举所有区间,只要维护一个区间最值,就可以去掉一维
区间的右端点所在的区间是个动态区间
要包含j个区间,最小的数是j*m个
}
}