洛谷试炼场 动态规划专练

// 最近才发现洛谷也有整理好的分类题集,于是前来加强练习一番。
普及练习场


在普及练习场分分钟解决了动态规划的背包问题,最后卡在 -->

P1064 金明的预算方案

重学了一遍分组背包终于解决了该问题。 参考背包问题九讲
注意分组后不能重复选啊!!!
 

解题思路

将附件做01背包求得在不同金钱下购买若干附件获得的最大价值,将所有方案加入到主件。现在新的主件中只能选择购买其中一种,于是转化为分组背包问题。
 

AC代码

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;

struct Node {
	int v, p;   // 花费 与 价值
};
vector<Node> Main[64];
vector<Node> Part[64];

int dp[33000];
int main() {
	int V, m;
	cin>>V>>m;
	for(int i=1;i<=m;i++) {
		int v, p, q;
		scanf("%d %d %d", &v, &p, &q);
		p *= v;
		if(!q) Main[i].push_back((Node){v, p});
		else
			Part[q].push_back((Node){v, p});
	}
	for(int i=1;i<=m;i++) {
		if(Part[i].size()==0) continue;

		// i主件的j附件 01背包
		memset(dp, 0, sizeof(dp));
		for(int j=0;j<Part[i].size();j++) {
			int v = Part[i][j].v, p = Part[i][j].p;
			for(int k=V;k>=v;k--) {
				dp[k] = max(dp[k], dp[k-v]+p);
			}
		}
		// 金钱j+Main[i][0].v 最多获得 dp[j]+Main[i][0].p
		// 全部放入Main分组
		int last = 0;
		for(int j=1;j+Main[i][0].v<=V;j++) {
			if(dp[j]>last)
				Main[i].push_back((Node){j+Main[i][0].v, dp[j]+Main[i][0].p}), last = dp[j];
		}
	}

	// 分组背包
	memset(dp, 0, sizeof(dp));
	for(int i=1;i<=m;i++) {
		for(int k=V;k>=0;k--) {
			for(int j=0;j<Main[i].size();j++) {
				int v = Main[i][j].v, p = Main[i][j].p;
				if(k>=v)
					dp[k] = max(dp[k], dp[k-v]+p);
			}
		}
	}
	printf("%d\n", dp[V]);
    return 0;
}

线性动态规划

 

P1020 导弹拦截

思路:单次拦截导弹个数就是求(不上升的) LIS , 总的拦截次数怎么求呢 ? 尝试贪心莽了一次发现第二问几乎都WA了。一看题解就是求(上升的) LIS 。。。
Why?

             西江月 · 证明

即得易见平凡,仿照上例显然。留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立,略去过程 Q E D,由上可知证毕。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int h[100010];
int dp[100010];
int main() {
	int n = 0, v;
	while(scanf("%d", &v)!=EOF) {
		h[++n] = -v;
	}

	dp[1] = h[1];
	int len1 = 1;
	for(int i=2;i<=n;i++) {
	    if(dp[len1]<=h[i]) dp[++len1] = h[i];
	    else {
		    int pos = upper_bound(dp+1, dp+len1+1, h[i]) - dp;
		    dp[pos] = h[i];
	    }
	}
	printf("%d\n", len1);

	for(int i=1;i<=n;i++)
		h[i] = -h[i];
		
	dp[1] = h[1];
	len1 = 1;
	for(int i=2;i<=n;i++) {
	    if(dp[len1]<h[i]) dp[++len1] = h[i];
	    else {
		    int pos = lower_bound(dp+1, dp+len1+1, h[i]) - dp;
		    dp[pos] = h[i];
	    }
	}
	printf("%d\n", len1);

	return 0;
}

 

P1091 合唱队形

思路:分别求出从前往后与从后往前到达第 i 位 的最长上升子序列长度 LIS1[i], LIS2[i],那么 ans = max(n+1 - LIS1[i] - LIS2[i]) 。
此题 n 较小,O(n^2)可以通过。
以下为 O(nlogn)复杂度 求 LIS 的算法。
代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

int h[110], n;
int d1[110], d2[110];		// 辅助数组
int LIS1[110], LIS2[110];	// LIS[i] : 以h[i]结尾的最长LIS长度 

void getLIS(int d[], int LIS[]) {
	d[1] = h[1]; LIS[1] = 1;
	int len = 1;
	// cout<<LIS[1];
	for(int i=2;i<=n;i++) {
		if(h[i]>d[len]) d[++len] = h[i], LIS[i] = len;
		else {
			int pos = lower_bound(d+1, d+len+1, h[i]) - d;
			d[pos] = h[i];
			LIS[i] = pos;
		}
		// cout<<' '<<LIS[i];
	}
	// puts("");
}

int main() {
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>h[i];

	getLIS(d1, LIS1);

	reverse(h+1, h+1+n);
	getLIS(d2, LIS2);
	reverse(LIS2+1, LIS2+1+n);

	int ans = n;
	for(int i=1;i<=n;i++) {
		ans = min(ans, n+1-(LIS1[i]+LIS2[i]));
	}
	printf("%d\n", ans);
	return 0;
}

 

P1880 [NOI1995]石子合并

思路:区间DP 模板题变形,区间变成了环形区间,加一个取模操作即可。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int h[110];
int sum[110][110];
int dp[110][110];

int main() {
	int n; cin>>n;
	for(int i=0;i<n;i++)
		scanf("%d", &h[i]);

	for(int i=0;i<n;i++) {
		sum[i][i] = h[i];
		for(int j=i+1;j<i+n;j++) {
			sum[i][j%n] = sum[i][(j-1+n)%n] + h[j%n];
		}
	}

	// for(int i=0;i<n;i++) {
	// 	for(int j=0;j<n;j++)
	// 		printf("%d%c", sum[i][j], j==n-1?'\n':' ');
	// }

	memset(dp, 0x3f, sizeof(dp));
	for(int i=0;i<n;i++) dp[i][i] = 0;
	int ans = 0x3f3f3f3f;
	for(int len=1;len<n;len++) {
		for(int i=0;i<n;i++) {
			int j = i+len;
			for(int k=i;k<j;k++) {
				dp[i][j%n] = min(dp[i][j%n], dp[i][k%n]+dp[(k+1)%n][j%n] + sum[i][j%n]);
				if(len==n-1)
					ans = min(ans, dp[i][j%n]);
			}
		}
	}
	printf("%d\n", ans);

	memset(dp, 0, sizeof(dp));
	for(int len=1;len<n;len++) {
		for(int i=0;i<n;i++) {
			int j = i+len;
			for(int k=i;k<j;k++) {
				dp[i][j%n] = max(dp[i][j%n], dp[i][k%n]+dp[(k+1)%n][j%n] + sum[i][j%n]);
				ans = max(ans, dp[i][j%n]);
			}
		}
	}
	printf("%d\n", ans);

	return 0;
}

 

P1140 相似基因

思路:设 dp[i][j] 表示 碱基S前 i 位与碱基T前 j 位匹配的最大相似度。
dp[i][j] = max{dp[i-1][j-1] + S[i]与T[j]的相似度, dp[i][j-1] + 空缺与T[j]的相似度, dp[i-1][j] + S[i]与空缺的相似度}
代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

char S[110], T[110];
int n, m;
int dp[110][110]; // S[i] 与 T[j] 位匹配的最大值
const int v[5][5] = {
	{5, -1, -2, -1, -3},
	{-1, 5, -3, -2, -4},
	{-2, -3, 5, -2, -2},
	{-1, -2, -2, 5, -1},
	{-3, -4, -2, -1, 0}
};

int id(char c) {
	if(c=='A') return 0;
	else if(c=='C') return 1;
	else if(c=='G') return 2;
	else if(c=='T') return 3;
	else return 4;
}
int main() {
	cin>>n>>S+1;
	cin>>m>>T+1;

	for(int i=1;i<=n;i++) S[i] = id(S[i]);
	for(int i=1;i<=m;i++) T[i] = id(T[i]);

	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			dp[i][j] = -0x3f3f3f;
	dp[0][0] = 0;
	for(int i=1;i<=m;i++) dp[0][i] = dp[0][i-1] + v[4][T[i]];
	for(int i=1;i<=n;i++) dp[i][0] = dp[i-1][0] + v[S[i]][4];

	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			dp[i][j] = max(dp[i][j], dp[i-1][j-1] + v[S[i]][T[j]]);
			dp[i][j] = max(dp[i][j], dp[i-1][j] + v[S[i]][4]);
			dp[i][j] = max(dp[i][j], dp[i][j-1] + v[4][T[j]]);
		}
	}
	printf("%d\n", dp[n][m]);
	return 0;
}
posted @ 2019-10-22 17:22  izcat  阅读(339)  评论(0编辑  收藏  举报