A - Max Sum Plus Plus HDU - 1024
题意:在一个长度为n的序列中找出m个互不相交的区间,是它们的和最大。
思路:dp[i][j]表示以a[i]作为第 j 个区间的右端点所能获得的最大值。
动态转移方程:dp[i][j] = max(dp[i - 1][j], dp[k][j - 1]) (k 大于0小于 i ),该转移方程表示以 i 作为第 j 个区间的结尾可把a[i] 合并到以 i - 1作为第 j 个区间的结尾得到或者 a[i] 作为一个区间得到。
复杂度分析:若直接暴力写的代码是这样的
for(int j = 1; j <= m; j++){
for(int i = j; i <= n; i++){ // 这里要i = j,因为第 j 个区间至少是从第 j 个数开始的
dp[i][j] = dp[i - 1][j] + a[i]; // 合并到上一个区间
for(int k = 1; k < i; k++){
dp[i][j] = max(dp[i][j], dp[k][j - 1] + a[i]);
}
}
}
空间复杂度是n * m, 时间复杂度是 m * n ^ 2, 哦豁,完蛋,空间时间都不够。
我们先来优化时间,我们发现这步其实就是找上一维 1 到 i - 1 的最大值, 那我们在遍历 n 的时候就可以顺便记录最大值了,然后就直接拿来用,不用一个个找
for(int k = 1; k < i; k++){
dp[i][j] = max(dp[i][j], dp[k][j - 1] + a[i]);
}
接下来是空间,当然是滚动数组优化, 这样空间复杂度就是n, 时间复杂度就是 n * m
这是我自己写的代码, 好像int就能过,不过保险起见我开了LL
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 50;
LL INF = 1e18;
LL a[maxn];
LL dp[maxn];
int main(int argc, char const *argv[])
{
int n, m;
while(~scanf("%d%d", &m, &n)){
for(int i = 1; i <= n; i++){
scanf("%I64d", &a[i]);
dp[i] = 0;
}
for(int j = 1; j <= m; j++){
LL ma = -INF;
for(int i = 1; i <= n; i++){
LL x = dp[i];
if(i >= j){
dp[i] = max(dp[i - 1] + a[i], ma + a[i]);
} else{
dp[i] = -INF;
}
ma = max(ma, x);
}
}
LL ans = -INF;
for(int i = m; i <= n; i++){
ans = max(ans, dp[i]);
}
printf("%I64d\n", ans);
}
return 0;
}
这是巨巨的代码
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 50;
LL INF = 1e18;
LL a[maxn];
LL dp[maxn];
LL ma[maxn];
int main(int argc, char const *argv[])
{
int n, m;
while(~scanf("%d%d", &m, &n)){
for(int i = 1; i <= n; i++){
scanf("%I64d", &a[i]);
dp[i] = ma[i] = 0;
}
LL MA;
for(int j = 1; j <= m; j++){
MA = -INF;
for(int i = j; i <= n; i++){
dp[i] = max(dp[i - 1], ma[i - 1]) + a[i];
ma[i - 1] = MA;
MA = max(MA, dp[i]);
}
}
printf("%I64d\n", MA);
}
return 0;
}
B - Ignatius and the Princess IV HDU - 1029
思路:利用好(n + 1) / 2这个条件就好了
#include<cstdio>
using namespace std;
int main()
{
int n;
while(~scanf("%d", &n)){
int cnt = 0;
int ans = 0;
for(int i = 0; i < n; i++){
int x;
scanf("%d", &x);
if(cnt == 0){
ans = x;
cnt = 1;
} else{
if(ans == x){
cnt++;
} else{
cnt--;
}
}
}
printf("%d\n", ans);
}
return 0;
}
C - Monkey and Banana HDU - 1069
思路:dp[i][j]表示第 i 个箱子在第 j 层时的最大高,因为n最大是30,我们把每个箱子看成三个箱子,在一个塔中,一个箱子最多出现两次(自己画一下,很容易看出来),所以最高是n * 6层的塔
状态转移方程:dp[i][cnt] = max(dp[i][cnt], dp[j][cnt - 1] + blocks[i].h);
复杂度分析:因为最高是6 * n层,所以复杂度为 6 * n * 3 * n * 3 * n = 54 * n ^ 3
#include<cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 50;
struct date
{
int ba1, ba2, h;
} blocks[maxn];
int dp[200][200];
int main()
{
int n;
int ca = 1;
while(~scanf("%d", &n)){
if(n == 0){
break;
}
int k = 0;
for(int i = 0; i < 200; i++){
for(int j = 0; j < 200; j++){
dp[i][j] = 0;
}
}
int ans = 0;
for(int i = 1; i <= n; i++){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
blocks[++k].ba1 = a, blocks[k].ba2 = b, blocks[k].h = c;
dp[k][1] = c;
blocks[++k].ba1 = b, blocks[k].ba2 = c, blocks[k].h = a;
dp[k][1] = a;
blocks[++k].ba1 = a, blocks[k].ba2 = c, blocks[k].h = b;
dp[k][1] = b;
ans = max(ans, max(a, max(b, c)));
}
int cnt = 2;
while(cnt){
int ma = 0;
for(int i = 1; i <= k; i++){
for(int j = 1; j <= k; j++){
if(max(blocks[i].ba1, blocks[i].ba2) < max(blocks[j].ba1, blocks[j].ba2) && min(blocks[i].ba1, blocks[i].ba2) < min(blocks[j].ba1, blocks[j].ba2)){
dp[i][cnt] = max(dp[i][cnt], dp[j][cnt - 1] + blocks[i].h);
ma = max(dp[i][cnt], ma);
}
}
}
cnt++;
if(cnt == n * 6 + 1){
break;
}
ans = max(ans, ma);
}
printf("Case %d: maximum height = %d\n", ca++, ans);
}
return 0;
}
D - Doing Homework HDU - 1074
思路:状压dp,用pre记录路径,题目要求输出的字典序最小,所以我们要让字典序大的尽量在后面解决
状态转移方程:dp[i].cost = min(dp[from].cost + c, dp[i].cost)
#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e6 + 50;
int INF = 1e8;
struct node
{
int pre, cost, now, time;
} dp[1 << 15];
struct date
{
string s;
int d, t;
} a[20];
void print(int t){ // 递归输出路径
int pre = dp[t].pre;
if(dp[pre].now != -1){
print(pre);
}
cout << a[dp[t].now].s << endl;
}
int main()
{
int tt;
cin >> tt;
while(tt--){
int n;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i].s >> a[i].d >> a[i].t;
}
dp[0].now = -1;
for(int s = 1; s < (1 << n); s++){
dp[s].cost = INF;
for(int i = 1; i <= n; i++){
if(s & (1 << (i - 1))){
int from = s - (1 << (i - 1));
int c = dp[from].time + a[i].t - a[i].d;
if(c < 0){
c = 0;
}
if(dp[s].cost >= dp[from].cost + c){ // 因为要字典序最小,所以这里必须是 >=
dp[s].cost = dp[from].cost + c;
dp[s].now = i;
dp[s].time = dp[from].time + a[i].t;
dp[s].pre = from;
}
}
}
}
int ans = dp[(1 << n) - 1].cost;
printf("%d\n", ans);
print((1 << n) - 1);
}
return 0;
}
E - Super Jumping! Jumping! Jumping! HDU - 1087
思路:dp[i]表示以 i 为结束的点所能获得的最大价值
状态转移方程:dp[i] = max(dp[j] + a[i], dp[i]);(a[i] > a[j])
#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e6 + 50;
int INF = 1e8;
int a[maxn];
int dp[1005];
int main()
{
int n;
while(~scanf("%d", &n)){
if(n == 0){
break;
}
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
dp[i] = a[i];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j < i; j++){
if(a[j] < a[i]){
dp[i] = max(dp[j] + a[i], dp[i]);
}
}
}
int ans = -INF;
for(int i = 1; i <= n; i++){
ans = max(ans, dp[i]);
}
printf("%d\n", ans);
}
return 0;
}
F - Piggy-Bank HDU - 1114
思路:类似于完全背包,但状态转移时要判断一下能不能转移
状态转移方程:**dp[j] = min(dp[j], dp[j - a[i].w] + a[i].p);(dp[j - a[i].w] != INF)**意思时在
**dp[j - a[i].w]**存在时才可以转移
#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e6 + 50;
int INF = 1e8;
int dp[10005];
struct date
{
int p, w;
} a[maxn];
bool cmp(date A, date B){
return A.w < B.w;
}
int main()
{
int t;
scanf("%d", &t);
while(t--){
int e, f;
scanf("%d%d", &e, &f);
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d%d", &a[i].p, &a[i].w);
}
sort(a + 1, a + n + 1, cmp);
int up = f - e;
for(int i = 1; i <= up; i++){
dp[i] = INF;
}
for(int i = 1; i <= n; i++){
for(int j = a[i].w; j <= up; j++){
if(dp[j - a[i].w] != INF){
dp[j] = min(dp[j], dp[j - a[i].w] + a[i].p);
}
}
}
if(dp[up] == INF){
printf("This is impossible.\n");
} else{
printf("The minimum amount of money in the piggy-bank is %d.\n", dp[up]);
}
}
return 0;
}
G - 免费馅饼 HDU - 1176
思路:这题正着dp会发现状态转移很难写,因为有些状态时不能到达的,所以我们反着dp,最后输出dp[0][5]的值就好了,dp[i][j]表示在时间 i 的时候处于 点 j 可获得的最大数量
状态转移方程:
dp[i][0] = max(dp[i + 1][0], dp[i + 1][1]) + num[i][0];
dp[i][j] = max(dp[i + 1][j - 1], max(dp[i + 1][j], dp[i + 1][j + 1])) + num[i][j];(1 <= i <= 9)
dp[i][10] = max(dp[i + 1][10], dp[i + 1][9]) + num[i][10];
#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e5 + 50;
int INF = 1e8;
int dp[maxn][20];
int num[maxn][20];
int main()
{
int n;
while(~scanf("%d", &n)){
if(n == 0){
break;
}
int mat = 0;
for(int i = 0; i <= maxn; i++){
for(int j = 0; j <= 10; j++){
dp[i][j] = num[i][j] = 0;
}
}
for(int i = 1; i <= n; i++){
int t, x;
scanf("%d%d", &x, &t);
num[t][x]++;
mat = max(mat, t);
}
for(int i = mat; i >= 0; i--){
dp[i][0] = max(dp[i + 1][0], dp[i + 1][1]) + num[i][0];
for(int j = 1; j < 10; j++){
dp[i][j] = max(dp[i + 1][j - 1], max(dp[i + 1][j], dp[i + 1][j + 1])) + num[i][j];
}
dp[i][10] = max(dp[i + 1][10], dp[i + 1][9]) + num[i][10];
}
printf("%d\n", dp[0][5]);
}
return 0;
}
H - Tickets HDU - 1260
思路:dp[i][0]表示第 i 个人买个人票所用的最小时间, dp[i][1]表示第 i 个人买双人票所用的最小时间
min(dp[n - 1][1], dp[n][0]) 就是所需的最小时间了
状态转移方程:
dp[i][0] = min(dp[i - 1][0], dp[i - 2][1]) + a[i].one;
dp[i][1] = min(dp[i - 1][0], dp[i - 2][1]) + a[i].two;
#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 1e5 + 50;
int INF = 1e8;
struct date
{
int one, two;
} a[maxn];
int dp[maxn][2];
int main()
{
int t;
scanf("%d", &t);
while(t--){
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++){
dp[i][0] = dp[i][1] = INF;
scanf("%d", &a[i].one);
}
for(int i = 1; i < n; i++){
scanf("%d", &a[i].two);
}
a[n].two = INF;
dp[1][0] = a[1].one;
dp[1][1] = a[1].two;
dp[0][0] = dp[0][1] = INF;
for(int i = 2; i <= n; i++){
dp[i][0] = min(dp[i - 1][0], dp[i - 2][1]) + a[i].one;
dp[i][1] = min(dp[i - 1][0], dp[i - 2][1]) + a[i].two;
}
int res = min(dp[n - 1][1], dp[n][0]);
int t = 8 * 60 * 60;
t += res;
int h = t / 3600;
t -= h * 3600;
int m = t / 60;
t -= m * 60;
printf("%02d:%02d:%02d ", h, m, t);
if(h >= 12){
printf("pm\n");
} else{
printf("am\n");
}
}
return 0;
}
I - 最少拦截系统 HDU - 1257
思路:贪心就完事了。。。
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
int a[1000000] = {0};
int main(){
int t;
while(~scanf("%d", &t)){
int x;
int flag = 1;
memset(a, 0, sizeof(a));
for(int i = 0; i < t; i++){
scanf("%d", &x);
int j;
for(j = 0; j < flag; j++){
if(a[j] >= x){
a[j] = x;
break;
}
}
if(j == flag){
a[flag++] = x;
}
}
printf("%d\n", flag - 1);
}
return 0;
}
J - FatMouse’s Speed HDU - 1160
思路:类似于最长上升子序列,这里n只用1000,n ^ 2的复杂度就够了,因为要记录路径,我们加一个pre数组
状态转移方程:
dp[i] = max(dp[i], dp[j] + 1) (a[j].w < a[i].w && a[j].s > a[i].s)
#include<cstdio>
#include <algorithm>
#include <string>
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
const int maxn = 1e5 + 50;
int INF = 1e8;
struct date
{
int id;
int w, s;
} a[maxn];
bool cmp(date A, date B){
if(A.w != B.w){
return A.w < B.w;
} else{
return A.s < B.s;
}
}
int dp[maxn];
int pre[maxn];
int main()
{
int n = 0;
int w, s;
while(~scanf("%d%d", &w, &s)){
a[++n].w = w;
a[n].s = s;
a[n].id = n;
}
sort(a + 1, a + n + 1, cmp);
dp[1] = 1;
int ans = 1;
for(int i = 1; i <= n; i++){
dp[i] = 1;
for(int j = 1; j < i; j++){
if(a[j].w < a[i].w && a[j].s > a[i].s){
if(dp[i] < dp[j] + 1){
dp[i] = dp[j] + 1;
ans = max(ans, dp[i]);
pre[i] = j;
}
}
}
}
printf("%d\n", ans);
int st = 0;
for(int i = n; i >= 1; i--){
if(dp[i] == ans){
st = i;
break;
}
}
stack<int> stk;
stk.push(a[st].id);
while(pre[st]){
st = pre[st];
stk.push(a[st].id);
}
while(stk.size()){
int x = stk.top();
stk.pop();
printf("%d\n", x);
}
return 0;
}