动态规划分析

一、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个	
		}
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值