线性DP专练(双十一篇)

首先让我们来复习一下最大上升子序列

portal : Longest Ordered Subsequence
首先想暴力做法,就是每次遍历到一个元素,要找以当前元素为结尾的最长上升序列,就是要在前面元素中找到一个结尾元素小于当前元素,并且最大长度的那个。如果用f[i]表示前i个数的最长长度
在这里插入图片描述

for(int i=0;i<n;i++){
		dp[i]=1;
		for(int j=0;j<i;j++){
			if(x[j]<x[i]){
				dp[i]=max(dp[i],dp[j]+1);
			}			
		} 
		m = max(m,dp[i]);
	}
	printf("%d",m);

稍微想一下就知道这个复杂度不得行。但是如果要记录上升轨迹就必须严格dp,这里只用求LIS长度,所以不需要严格dp。
那么怎么办呢?
首先把数列的第一个元素丢到dp数组(存放上升子序列)中,找个index指向dp数组中的最后一个元素,如果数列后面有元素>index所指元素,则扩充dp数组;否则就在dp数组中二分查找严格小于该数的第一个数,用本数替换掉本来在那个小数的正后方的那个数。
在这里插入图片描述

#include<iostream>
#include<vector>
using namespace std;
int n;
int main(int argc, char const *argv[])
{
	cin >> n;
	vector<int> dp(n, 0); vector<int> s(n, 0);
	for (int k = 0; k < n; ++k) cin >> s[k];
	int i = 0, j = s[0];//i就是那个index
	dp[i] = j;
	for (int k = 1; k < n; ++k){
		if(s[k] > dp[i]) dp[++i] = s[k];//严格大于dp的最后一个元素就扩充dp
		else{
			int l = 0, r = i, mid;
			while(l < r){
				mid = (l + r) >> 1;
				if(dp[mid] > s[k]) r = mid;
				else l = mid + 1;
			}
			dp[l] = s[k];//找到上图中9的位置,把3替换过去
		}
	}
	cout <<  i + 1 << endl;//i是从0开始的
	return 0;
}

注意:
①每次替换的一定是dp数组中比该数小的最大数的右边的数。
②此法dp记录的递增序列不一定是按照原序列顺序(详见紫色批注部分)

再来看一道裸的线性dp:买电脑

portal : Computers

#include<iostream>
#define INF 1 << 29
using namespace std;
int n, c;
int f[10005], m[10005][10005];
int main(int argc, char const *argv[])
{
	while(~scanf("%d%d", &c, &n)){
		f[0] = 0;
		for(int i = 1; i <= n; i++) f[i] = INF;
		//要求最小,则初始化为最大
		for (int i = 1; i <= n; ++i){
			for (int j = i; j <= n; j++) scanf("%d", &m[i][j]);//m[i][j]表示第i年到第j年的维护费用
		}
		for (int i = 1; i <= n ; ++i){
			for (int j = 1; j <= i; j++){
				f[i] = min(f[i], f[j - 1] + m[j][i] + c);
				//如果要买电脑的花费=前(j-1)年费用+第j年到第i年的维护费用+买电脑的费用
			}
		}
		printf("%d\n", f[n]);
	}
	return 0;
}

足够裸,裸到O(n^2)都过了。。
其实这题给我最大的启发就是当下最优解=前面子问题的最优解+转移代价
然后再来看wx小哥哥拉的dp专练~

多段连续字段和最大值

portal : max sum plus plus
我们来回忆一下之前最大子段和我们是怎么解决的?是不是每次要么附加在我们的已选数组最后,要么另起炉灶,然后每次滚一遍最大和。
那么我们多段怎么处理呢?是不是要么附加在最后一段后面(与最后一段合并),要么另起一段?
这题我感觉是属于那种说起来简单然后代码有点无从下手的。
在这里插入图片描述

#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
#define INF 1 << 29//定义无穷大的好方法
const int N = 1000005;
int d[N], pre[N], num[N];
int m, n;
int main(int argc, char const *argv[]){
	while(~scanf("%d%d", &m, &n)){
		memset(d+1, 0, n * 4);//从第一个开始memset,注意第三个参数是字节数
		memset(pre+1, 0, n * 4);
		int tmp;
		for(int i = 1; i <= n; i++) scanf("%d", &num[i]);
		for(int i = 1; i <= m; i++){
			tmp = -INF;
			for(int j = i; j <= n; j++){
				d[j] = max(d[j-1], pre[j-1]) + num[j];
				pre[j-1] = tmp;//注意这里更新的是pre[j-1]
				tmp = max(tmp, d[j]);
			}
		}
		printf("%d\n", tmp);
	}
	return 0;
}

i=1时,pre记录的是一段的最大和;
i=2时,pre被两段最大和更新,但是d依旧用之前的pre计算(因为目前算两段,新起了一段,前面只需要一段)
以此类推…

1)解锁memset新用法:从想开始的位置开始,初始化想要的个数,同时注意只能全赋为0或-1,全赋1会出问题。因为memset是一个字节一个字节设置的,取要赋的值的后8位二进制进行赋值。1的二进制是(00000000 00000000 00000000 00000001),取后8位(00000001),int型占4个字节,当初始化为1时,它把一个int的每个字节都设置为1,也就是0x01010101,二进制是00000001 00000001 00000001 00000001,十进制就是16843009。所以第三个参数要填字节数。
2)若每次更新只涉及 i 和 i - 1 之间的取舍,那么完全可以利用滚动数组省去一维空间。
3)定义无穷大可用#define INF 1 << 29

又是一道裸题。。吗?

Portal : Ignatius and the Princess IV
芜湖,先暴力然后贡献了一发TLE~那好吧,再看看题。
en 结构体数组是个好东西,而且貌似int的范围在1e6之内,可以用数组下标存数,数组值存出现次数。

#include<iostream>
#include<string.h>
using namespace std;
const int N = 1e6 + 10;
int book[N], arr[N];//book[x]记录数x的位置
struct node{
	int x, ans;
}dp[N];//记录数x和出现次数
int n;
int main(int argc, char const *argv[]){
	while(~scanf("%d", &n)){
		int m = (1 + n) / 2;
		memset(book, -1, sizeof book);
		memset(dp, 0, sizeof dp);
		int cnt = 0;
		for(int i = 0; i < n; i++) scanf("%d", &arr[i]);
		for(int i = 0; i < n; i++){
			int a = arr[i];
			int index = book[a];
			if(index == -1){//如果没出现过
				book[a] = cnt;
				dp[cnt].x = a;
				dp[cnt++].ans++;
			}else{//如果出现过
				dp[index].ans++;
			}
			if(dp[index].ans >= m){
				printf("%d\n", dp[index].x);
				break;
			}
		}
	}
	return 0;
}

其实这一题有一种既神奇又简单的解法——我愿称之为“一瞄法”,你看它给的数字个数为奇,所以(1+n)/2必为偶数,排序之后,无论那个出现次数>=(1+n)/2的数在哪里,它总会经过(1+n)/2的下标,所以就捉住你啦~
在这里插入图片描述其实多审审题,多动动脑,可以省很多代码,少费许多功夫呢!这种简单方法多省事!

再来一题!

Portal : Monkey and Banana
这题最奇怪一点是自己可以转一下方向然后堆在自己上面,而且数量不限,但是仔细一想你就会发现,自己可以堆在自己上面的情况屈指可数。仔细读题会发现一次堆箱子的个数不会超过30,为什么呢?我们是不是可以把转一下之后的情况算作不同长宽高的箱子,反正空间多的是,而且绝对够。

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int dp[110];
struct node{
	int l, w, h;//length,width,height
}box[110];
bool cmp(node a, node b){
	if(a.l != b.l) return a.l > b.l;//长更长的放前面
	else return a.w > b.w;//宽更宽的放前面
}
int n;
int main(int argc, char const *argv[]){
	int a, b, c;
	int g = 0;
	while(cin >> n, n){//当n为0时不再读入
		int k = 0;
		memset(dp, 0, sizeof dp);
		memset(box, 0, sizeof box);
		for(int i = 1; i <= n; i++){
			cin >> a >> b >> c;
			box[++k].l = a; box[k].w = b; box[k].h = c;//按高分类,三种,要么c
			if(box[k].l < box[k].w) swap(box[k].l, box[k].w);//保证箱子的长>宽
			box[++k].l = b; box[k].w = c; box[k].h = a;//按高分类,三种,要么a
			if(box[k].l < box[k].w) swap(box[k].l, box[k].w);//保证箱子的长>宽
			box[++k].l = c; box[k].w = a; box[k].h = b;//按高分类,三种,要么b
			if(box[k].l < box[k].w) swap(box[k].l, box[k].w);//保证箱子的长>宽
		}
		box[0].l = 1e9, box[0].w = 1e9, box[0].h = 0;//第一个长宽无限,高为0
		sort(box+1, box+k+1, cmp);
		int maxx = 0;
		for(int i = 1; i <= k; i++){
			for(int j = 0; j < i; j++){
				if(box[i].l < box[j].l && box[i].w < box[j].w){//若是后面箱子长宽严格小于前面箱子则可摞
					dp[i] = max(dp[i], dp[j] +box[i].h);
					maxx = max(maxx, dp[i]);
				}
			}
		}
		g++;
		cout << "Case " << g << ": maximum height = " << maxx << endl;
	}
	return 0;
}

其实这题也很暴力,主要是要想到怎么排序,怎么处理这些箱子。
其实这篇blog10月中旬就动笔了,到现在才写完,我真的要好好反省下了┭┮﹏┭┮
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值