// 最近才发现洛谷也有整理好的分类题集,于是前来加强练习一番。
在普及练习场分分钟解决了动态规划的背包问题,最后卡在 -->
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
", 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
", 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
", 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
", 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?'
':' ');
// }
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
", 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
", 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
", dp[n][m]);
return 0;
}