题目链接:https://vjudge.net/article/187
1、
Max Sum Plus Plus
题目大意:给出n个数,求出m段不重复连续子串和的最大值。
(看完题目后并没有思路,于是查了博客=。=)
思路:
状态:dp[i][j]表示分成j块并以i为结尾的子段和最大值,mx[i][j]表示dp[0...i][0...j]的最大值。
划分策略:以a[i]单独为一组作为第j组,或a[i]作为前面组的一部分。
状态转移方程:如下。
1 //dp[i][j]意思为分成j块以a[i]结尾的和最大值,mx[i][j]存储着dp[j~i][j]中的最大值。
2 //划分策略为以a[i]单独为一组作为第j组,或a[i]作为前面组的一部分,所以状态转移方程为
3 //dp[i][j] = max(dp[i-1][j], mx[i-1][j-1]) + a[i]
4 //mx[i][j] = max(dp[i][j], mx[i-1][j])
5 // for (int j=1;j<=m;j++)
6 // {
7 // for (int i=j;i<=n;i++)
8 // {
9 // if (i == j) dp[i][j] = dp[i-1][j-1] + a[i];
10 // else dp[i][j] = max(dp[i-1][j] + a[i], mx[i-1][j-1] + a[i]);
11 // mx[i][j] = max(mx[i-1][j], dp[i][j]);
12 // }
13 // }
14 // for (int i=m;i<=n;i++)
15 // ans = max(ans, dp[i][m]);
空间压缩:但是题目范围不允许使用我们使用二维数组了,更何况两个,但是这里因为转移方程中调用的都是j-1或是i-1,所以我们可以压缩空间。dp[i] = max(dp[i-1], mx[i-1]) + a[i]中,i == j时,dp[i-1]存放的值为dp[i-1][j-1],mx[i-1]为mx[i-1][j-1];i != j时,dp[i-1]为dp[i-1][j], mx[i-1]为mx[i-1][j-1]。为了满足这个式子,dp数组只需照常填入即可,mx数组必须保证比dp数组慢一步,因为在填入dp[i]时必须保证mx[i-1]还未更新过,还没有从mx[i-1][j-1]变成mx[i-1][j]。
代码:
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 using namespace std;
5 const int M = 1e6+10;
6 int dp[M], mx[M];
7 int a[M], n, m;
8 int main()
9 {
10 while (~scanf("%d%d", &m, &n))
11 {
12 memset(dp, 0, sizeof(dp));
13 memset(mx, 0, sizeof(mx));
14 for (int i=1; i<=n; i++)
15 scanf("%d", &a[i]);
16 int q;
17 for (int j=1; j<=m; j++)
18 {
19 q = INT_MIN;
20 for (int i=j; i<=n; i++)
21 {
22 dp[i] = max(dp[i-1], mx[i-1]) + a[i];
23 mx[i-1] = q;
24 q = max(q, dp[i]);
25 }
26 }
27 printf("%d
", q);
28 }
29 }
总结:这个状态表示很巧妙。挺神奇的,这个状态是我以前没有写到过的。我目前对于dp的理解还很肤浅,解题思路都是从类似的题目偷过来的,而类似的题目最早是看答案的,意味着我不能独立思考dp问题,不过这一题让我对空间压缩有了一些自己的理解。继续努力吧,希望把这19题全部写完能对dp有自己的理解。
2、
Ignatius and the Princess IV
题目大意:给一个n,给n个数,找出n个数中出现(n+1)/2次以上的数。(n为奇数)
刚做完很好奇怎么用dp,于是又去别人博客看了一下=。=
思路:
O(nlogn)做法:将n个数排序,因为答案出现(n+1)/2次以上,排序后(n+1)/2位置必定是这个数。
O(n)做法:序列中任意两个数如果不相等就删去,原数组中的多元素在新序列中还是新元素。假设原序列中多元素仅为(n+1)/2,去掉一组中有多元素,则新序列中多元素还有(n-1)/2还是等于新序列中应有多元素数量(n-1)/2。
O(n)做法的细节:用result代表到目前为止的序列中的多元素,a[i]与result相同的计数器cnt++,不相同的cnt--,这一步可以看作是把之前的多元素拿出来和a[i]抵消掉,即思路中的删掉一组不相同的元素。
1 //O(n)
2 #include <cstdio>
3 #include <cstring>
4 using namespace std;
5 const int M = 1e6+5;
6 int t, n, result, cnt;
7
8 int main()
9 {
10 while (~scanf("%d", &n))
11 {
12 cnt = 0;
13 for (int i=1;i<=n;i++)
14 {
15 scanf("%d", &t);
16 if (cnt == 0)
17 {
18 cnt = 1;
19 result = t;
20 }else
21 {
22 if (t == result) cnt++;
23 else cnt--;
24 }
25 }
26 printf("%d
", result);
27 }
28 return 0;
29 }
30
31
32 //O(nlogn)
33 //#include <cstdio>
34 //#include <algorithm>
35 //using namespace std;
36 //const int M = 1e6+5;
37 //int a[M];
38 //
39 //int main()
40 //{
41 // int n;
42 // while(~scanf("%d", &n))
43 // {
44 // for (int i=1;i<=n;i++)
45 // scanf("%d", &a[i]);
46 // sort(a+1, a+1+n);
47 // int k = (n+1)/2;
48 // printf("%d
", a[k]);
49 // }
50 //
51 //}
总结:原本都把dp数组定义了,看了博客才知道不需要dp数组。算是第一次认识到动态规划是一种思想,用小问题的视角去看问题,而不是一种模板。
3、
Monkey and Banana
题目大意:给出n个具有三维属性的长方体,可以改变摆法,每种长方体有无限个,求出能堆叠的最高高度。堆叠必须满足每一层的尺寸严格小于其下一层的尺寸,及长和宽小于下一层的长和宽。
思路:每个长方体有6种摆法,将他们各自视作一种长方体。然后求能堆叠的最高高度。
状态:dp[i]表示以i为底座的最高高度。
状态转移方程:第j个长方体的尺寸满足在i上方的要求即(b[i].x > b[j].x && b[i].y > b[j].y)时,dp[i] = max(dp[i], dp[j]+b[i].v);
然后就可以通过i从1到6*n和j从1到i-1的循环来填写dp数组。答案就是dp数组中的最大值。
代码:
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <vector>
5 using namespace std;
6 const int M = 33 * 6;
7 struct box{
8 int x, y, v;
9 }b[M];
10 int dp[M], n;
11
12 void input(int, int, int, int);
13 bool cmp(box a, box b);
14
15 int main()
16 {
17 int T = 1;
18 while (scanf("%d", &n) && n)
19 {
20 memset(dp, 0, sizeof(dp));
21 for (int i=1;i<=n;i++)
22 {
23 int x, y, z;
24 scanf("%d%d%d", &x, &y, &z);
25 input(x, y, z, i);
26 }
27 sort(b+1, b+1+n*6, cmp);
28 int ans = 0;
29 for (int i=1;i<=6*n;i++)
30 {
31 dp[i] = b[i].v;
32 for (int j=1;j<=i-1;j++)
33 {
34 if (b[i].x > b[j].x && b[i].y > b[j].y)
35 dp[i] = max(dp[i], dp[j]+b[i].v);
36 }
37 if (dp[i] > ans)
38 ans = dp[i];
39 }
40 printf("Case %d: maximum height = %d
", T++, ans);
41 }
42 return 0;
43 }
44
45 void input(int x, int y, int z, int i)
46 {
47 b[6*(i-1)+1].x = x, b[6*(i-1)+1].y = y, b[6*(i-1)+1].v = z;
48 b[6*(i-1)+2].x = x, b[6*(i-1)+2].y = z, b[6*(i-1)+2].v = y;
49 b[6*(i-1)+3].x = y, b[6*(i-1)+3].y = x, b[6*(i-1)+3].v = z;
50 b[6*(i-1)+4].x = y, b[6*(i-1)+4].y = z, b[6*(i-1)+4].v = x;
51 b[6*(i-1)+5].x = z, b[6*(i-1)+5].y = x, b[6*(i-1)+5].v = y;
52 b[6*(i-1)+6].x = z, b[6*(i-1)+6].y = y, b[6*(i-1)+6].v = x;
53
54 }
55 bool cmp(box a, box b)
56 {
57 if (a.x == b.x) return a.y < b.y;
58 return a.x < b.x;
59 }
4、
Doing Homework
才发现少做了一题
5、
Super Jumping! Jumping! Jumping!
↑这个好像就是题目链接了,神奇
题目大意:求上升子序列和的最大值。
思路:没有思路,太水了,直接上代码。
代码:
1 //O(n^2)
2 #include <cstdio>
3 #include <cstring>
4 #include <algorithm>
5 using namespace std;
6 const int M = 1010;
7 int n, dp[M], a[M];
8
9 int main()
10 {
11 while (scanf("%d", &n) && n)
12 {
13 memset(dp, 0, sizeof(dp));
14 for (int i=1;i<=n;i++)
15 scanf("%d", &a[i]);
16 int ans = 0;
17 for (int i=1;i<=n;i++)
18 {
19 dp[i] = a[i];
20 for (int j=i-1;j>=1;j--)
21 {
22 if (a[j] < a[i])
23 dp[i] = max(dp[j]+a[i], dp[i]);
24 }
25 ans = max(ans, dp[i]);
26 }
27 printf("%d
", ans);
28 }
29 return 0;
30 }
6、
Piggy-Bank
题目大意:完全背包问题求最小值。
思路:就是完全背包问题,背包问题还是看背包九讲,我还是不献丑了。
代码:
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 using namespace std;
5 const int M = 1e4+5;
6 const int N = 505;
7 int w[N], v[N], dp[M], T, n, m;
8 int main()
9 {
10 scanf("%d", &T);
11 while (T--)
12 {
13 memset(dp, 0x3f, sizeof(dp));
14 dp[0] = 0;
15 int E, F;
16 scanf("%d%d", &E, &F);
17 m = F - E;
18 scanf("%d", &n);
19 for (int i=1;i<=n;i++)
20 scanf("%d%d", &v[i], &w[i]);
21 for (int i=1;i<=n;i++)
22 for (int j=w[i];j<=m;j++)
23 {
24 dp[j] = min(dp[j], dp[j-w[i]]+v[i]);
25 }
26 if (dp[m] != 0x3f3f3f3f)
27 printf("The minimum amount of money in the piggy-bank is %d.
", dp[m]);
28 else
29 printf("This is impossible.
");
30 }
31 return 0;
32 }
总结:0x3f3f3f3f真好用,可以用memset(x, 0x3f, sizeof(x))来把数组全部初始化为无穷大。
7、
免费馅饼
题目大意:不会有人中文还看不懂把? 有n个果子,第i个果子在ti时刻落在xi上。0<=xi<=11。起点即0s时,switch在x=5的位置,每一秒可以向左右移动一格。问最多能收集到多少个果子
思路:
状态:dp[i][j]表示在第i秒,第j个位置收集到的最大果子数
状态转移方程:dp[i][j] = max(dp[i+1][j+1], max(dp[i+1][j], dp[i+1][j-1])
细节:1、因为每秒钟可以向左右两边移动,那么我们将移动范围从0~11搬至1~12,这样可以安稳的写出状态转移式子。
2、正如状态转移方程描述的一样,第i秒是由第i+1秒转移过来的。为什么呢?因为起点是定的,而以前做的题目都是终点是定的。这样我们只需要最后取dp[0][6]的值即可。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M = 1e5+5;
int dp[M][15], n;
int main()
{
while (scanf("%d", &n) && n)
{
memset(dp, 0, sizeof(dp));
int mt = 0;
for (int i=1;i<=n;i++)
{
int x, t;
scanf("%d %d", &x, &t);
dp[t][x+1] ++;
mt = max(mt, t);
}
for (int i=mt-1;i>=0;i--)
{
for (int j=1;j<=12;j++)
{
dp[i][j] = max(dp[i+1][j], max(dp[i+1][j+1], dp[i+1][j-1])) + dp[i][j];
}
}
printf("%d
", dp[0][6]);
}
return 0;
}
题外话:这题最早是被我们学校OJ改了名字搬了,那时候我还不会做,于是就在网上找了题解。最近两天又做过一次,拿到手就直接写出来了。
8、
Tickets
题目大意:有n个人买票,给出n个s[i]代表每个人单独买票的时间消耗,给出n-1个d[i]代表每两个人一起买票的时间消耗,问怎么使得n个人买完票所用时间最小。
思路:每个人买票策略是,自己单独买,或者和上一个人组队买。
状态:dp[i]代表到第i个人所需的最短时间。
状态转移方程:dp[i] = min(dp[i-1] + s[i], dp[i-2] + d[i-1]);
代码:
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 using namespace std;
5 const int M = 2020;
6 int T, n, s[M], d[M], dp[M], dp2[M];
7
8 void in();
9 void out();
10
11 int main()
12 {
13 scanf("%d", &T);
14 while (T--)
15 {
16 memset(dp, 0x3f, sizeof(dp));
17 in();
18 dp[1] = s[1];
19 dp[0] = 0;
20 for (int i=2;i<=n;i++)
21 dp[i] = min(dp[i-1] + s[i], dp[i-2] + d[i-1]);
22
23 out();
24 }
25 }
26
27 void in()
28 {
29 scanf("%d", &n);
30 for (int i=1;i<=n;i++)
31 scanf("%d", &s[i]);
32 for (int i=1;i<=n-1;i++)
33 scanf("%d", &d[i]);
34 }
35 void out()
36 {
37 int t = dp[n];
38 int h = (t / 3600 + 8) % 12;
39 bool f = (t / 3600 + 8 >= 12);
40 int m = t / 60 % 60;
41 t %= 60;
42 printf("%02d:%02d:%02d ", h, m, t);
43 if (f) printf("pm
");
44 else printf("am
");
45 }
9、
最少拦截系统
题目大意:一个系统可以拦截导弹,但是每拦截一次导弹,可拦截高度就会变成这个导弹的高度。给出n个导弹的高度,问完全拦截下来需要多少套这个系统。
思路:虽然我用了dp数组,但这完全就是贪心噢。没得到一个导弹,往前找高度最低的系统。找不到系统数++,新系统高度为此导弹高度。
代码:
1 #include <cstdio>
2 #include <cstring>
3 #include <iostream>
4 using namespace std;
5 const int M = 1e5+5;
6 int dp[M], n, cnt;
7 int main()
8 {
9 while (~scanf("%d", &n))
10 {
11 cnt = 0;
12 int x;
13 for (int i=1;i<=n;i++)
14 {
15 scanf("%d", &x);
16 int mi, minh = 0x3f3f3f3f, f = false;
17 for (int i=1;i<=cnt;i++)
18 {
19 if (minh > dp[i] && dp[i] >= x)
20 {
21 minh = dp[i];
22 mi = i;
23 f = true;
24 }
25 }
26 if (f) dp[mi] = x;
27 else
28 {
29 //cout << "///////////////////////" <<endl;
30 cnt++;
31 dp[cnt] = x;
32 }
33 }
34
35 printf("%d
", cnt);
36 }
37 return 0;
38 }
10、
FatMouse's Speed
(这题题目看了我好久)
题目大意:给很多老鼠,每个老鼠有一个体重值和速度值。问你喜欢乡下的老鼠还是城市里的老鼠?有多少老鼠排成序列满足体重严格增加,速度严格减少。
思路:给老鼠按体重排序,再以速度为标准求最长下降子序列。因为数据比较小,o( n^2 )也可以过。
代码(代码有点丑):
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 using namespace std;
5 const int M = 1010;
6 struct node{
7 int w, s, pos;
8 }m[M];
9 int dp[M], f[M], num[M];
10 bool cmp(node a, node b);
11 int main()
12 {
13 int cnt = 1;
14 while (~scanf("%d %d", &m[cnt].w, &m[cnt].s))
15 f[cnt] = cnt, m[cnt].pos = cnt, cnt++;
16 cnt--;
17 sort(m+1, m+1+cnt, cmp);
18 // for (int i=1;i<=cnt;i++)
19 // printf("%d\\%d
", m[i].w, m[i].s);
20 int ans = 0, maxi;
21 for (int i=1;i<=cnt;i++)
22 {
23 dp[i] = 1;
24 for (int j=i-1;j>=1;j--)
25 {
26 if (m[j].s > m[i].s && dp[j] >= dp[i] && m[j].w < m[i].w)
27 dp[i] = dp[j] + 1, f[i] = j;
28 }
29 //printf("%d
", dp[i]);
30 if (ans < dp[i])
31 {
32 ans = dp[i];
33 maxi = i;
34 }
35 }
36 printf("%d
", ans);
37 cnt = ans;
38 for (;cnt > 0;cnt--)
39 {
40 num[cnt] = m[maxi].pos;
41 maxi = f[maxi];
42 }
43 for (int i=1;i<=ans;i++)
44 {
45 printf("%d
", num[i]);
46 }
47 return 0;
48 }
49 bool cmp(node a, node b)
50 {
51 return a.w < b.w;
52 }
11、
Jury Compromise
题目大意:
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n个人作为陪审团的候选人,然后再从这n个人中选m人组成陪审团。选m人的办法是:
控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0到20。为了公平起见,法官选出陪审团的原则是:选出的m个人,必须满足辩方总分和控方总分的差的绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控双方总分之和最大的方案即可。
思路:把这个过程看作01背包。
状态:dp[i][j][k]表示在选到第i个人时,选了j个人,辩方与控方总分之差为k时,辩控双方总分和最大。
状态转移方程:dp[i][j][k] = max(dp[i-1][j][k], dp[i-1][j-1][k-(p[i]-d[i])]+p[i]+d[i])。对于第i个人,可以选择选或是不选。
细节: 1、因为辩控双方评分差可能小于0,所以将[-400, 400]搬至[0, 800], 基准位置base也从0变成400。
2、初始化时,将dp数组中每一个元素都初始化为负无穷大。再将dp[0][0][base]初始化为0。这么初始化可以保证,我们每次转移的时候只填从已到达的位置可以到达的位置。
3、动态转移时,i = 1...n, j = 0...min(i, m)(因为可以一个也不取), k = 0...800。
4、输出时,原路返回,如果dp[i][j][v] == dp[i-1][j][v],说明这个i没有取,i--;不然则是已取,做出相应的处理。
5、空间还能优化,只要记录path数组,可以去掉dp数组中i的位置,4.19写。
代码:
1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 using namespace std;
5 const int M = 205, N = 805, base = 400;
6 int d[M], p[M], n, m, T, dp[M][21][N], num[21];
7
8 void f();
9
10 int main()
11 {
12 while (scanf("%d %d", &n, &m) && n)
13 {
14 T++;
15 memset(dp, -0x3f, sizeof(dp));
16 dp[0][0][base] = 0;
17 for (int i=1; i<=n; i++)
18 {
19 int x, y;
20 scanf("%d%d", &p[i], &d[i]);
21 }
22 for (int i=1; i<=n; i++)
23 {
24 for (int j=0; j<=m; j++)
25 {
26 for (int k=0; k<=800; k++)
27 {
28 dp[i][j][k] = dp[i-1][j][k];
29 int t = k - (p[i] - d[i]);
30 if (t < 0 || t > 800)
31 continue;
32 if (j < 1 || j > i) continue;
33 dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][t] + d[i] + p[i]);
34 }
35 }
36 }
37 printf("Jury #%d
", T);
38 f();
39 }
40 return 0;
41 }
42
43 void f()
44 {
45 int v = 0;
46 while (v<=400 && dp[n][m][base+v] < 0 && dp[n][m][base-v] < 0)
47 v++;
48 int i = n, j = m, psum = 0, dsum = 0;
49 v = dp[n][m][base+v] > dp[n][m][base-v] ? base+v : base-v;
50 //printf("v = %d
", v);
51 while (j)
52 {
53 if (dp[i][j][v] == dp[i-1][j][v])
54 i--;
55 else
56 {
57 num[j] = i;
58 psum += p[i];
59 dsum += d[i];
60 v -= (p[i] - d[i]);
61 i--;
62 j--;
63 }
64 }
65 printf("Best jury has value %d for prosecution and value %d for defence:
", psum, dsum);
66 for (int i = 1 ; i <= m; i++) {
67 printf(" %d", num[i]);
68 }
69 printf("
");
70 }
总结:感觉自己dp已经入门了,不过细节部分还是需要加强。做的时候虽然有思路,但是dp的过程中出错了,导致了很多次re,然后借鉴了一下别人的博客。总之还是得靠多刷题。
12、
Common Subsequence
题目大意:给定两个序列X和Y,问题是找到X和Y的最大长度公共子序列的长度。
代码:
1 #include <iostream>
2 #include <string>
3 #include <cstring>
4 using namespace std;
5 const int M = 1010;
6 string s1, s2;
7 int l1, l2, dp[M][M];
8
9 int main()
10 {
11 while (cin>>s1>>s2)
12 {
13 memset(dp, 0, sizeof(dp));
14 l1 = s1.size();
15 l2 = s2.size();
16
17 for (int i=1;i<=l1;i++)
18 for (int j=1;j<=l2;j++)
19 {
20 if (s1[i-1] == s2[j-1])
21 dp[i][j] = dp[i-1][j-1] + 1;
22 else
23 dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
24 }
25 cout << dp[l1][l2] << '
';
26 }
27 return 0;
28 }
13、
Help Jimmy
我还想不到怎么做=。=
14、
Longest Ordered Subsequence
题目大意:给一个序列,找出最长上升子序列长度。
思路:LIS,这次用的是贪心加二分查找。
代码:
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 const int M = 1010; 5 int a[M], n, dp[M]; 6 int main() 7 { 8 while (~scanf("%d", &n)) 9 { 10 memset(dp, 0, sizeof(dp)); 11 for (int i=1;i<=n;i++) 12 scanf("%d", &a[i]); 13 int cnt = 1; 14 dp[cnt] = a[1]; 15 for (int i=2;i<=n;i++) 16 { 17 int l = 1, r = cnt, mid; 18 while (l <= r) 19 { 20 mid = (l+r) >> 1; 21 if (a[i] <= dp[mid]) r = mid - 1; 22 else l = mid + 1; 23 } 24 dp[l] = a[i]; 25 if (l > cnt) cnt++; 26 } 27 printf("%d ", cnt); 28 } 29 return 0; 30 }
总结:二分有时间要好好巩固一下,很不熟练。
15、
Treats for the Cows
题目大意:给你n个数字v(1),v(2),...,v(n-1),v(n),每次你可以取出最左端的数字或者取出最右端的数字,一共取n次取完。假设你第i次取的数字是x,你可以获得i*x的价值。你需要规划取数顺序,使获得的总价值之和最大。
思路:使用区间DP。
状态:dp[i][j]代表从i取到j的最大值。
状态转移方程:dp[i][j] = max(dp[i+1][j]+a[i]*(n-j+i), dp[i][j-1]+a[j]*(n-j+i)。
代码;
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 const int M = 2020; 6 int n, a[M], dp[M][M]; 7 int main() 8 { 9 scanf("%d", &n); 10 for (int i=1;i<=n;i++) 11 scanf("%d", &a[i]); 12 for (int len=1;len<=n;len++) 13 { 14 for (int l=1;l<=n-len+1;l++) 15 { 16 dp[l][l+len-1] = max(dp[l+1][l+len-1] + a[l]*(n-len+1), dp[l][l+len-2] + a[l+len-1]*(n-len+1)); 17 } 18 } 19 printf("%d ", dp[1][n]); 20 return 0; 21 }
总结:以前从没想过用一个区间来表示状态,于是最开始做的时候还是用的dp[i][j]表示取第i个数的时候取走j得到的最大值。这样的状态转移非常不好写,我写了一两个小时还写不出来。dp是小状态聚集成大状态的过程。把我的脑瘫代码放在这警示我自己,纪念我的弱智青春。
1 //#include <cstdio> 2 //#include <cstring> 3 //#include <algorithm> 4 //using namespace std; 5 //const int M = 2020; 6 //int n, a[M], dp[M][M][2]; 7 //int main() 8 //{ 9 // scanf("%d", &n); 10 // for (int i=1;i<=n;i++) 11 // scanf("%d", &a[i]); 12 // int ans = 0; 13 // for (int i=1;i<=n;i++) 14 // { 15 // 16 // for (int j=1;j<=n;j++) 17 // { 18 // int l, r; 19 // if (i < (n+1)/2) 20 // { 21 // if (j > i && j < n-i+1) continue; 22 // if (j <= i) 23 // { 24 // l = j-1; 25 // r = n-i+j+1; 26 // }else if (j >= n-i+1) 27 // { 28 // l = i+j-n-1; 29 // r = j+1; 30 // } 31 // dp[i][j][0] = max(dp[i-1][l][0], dp[i-1][r][0]); 32 // dp[i][j][1] = max(dp[i-1][l][1], dp[i-1][r][1]); 33 // } 34 // else 35 // { 36 // dp[i][j][0] = dp[i-1][j-1][0]; 37 // dp[i][j][1] = dp[i-1][j-1][1]; 38 // } 39 // dp[i][j][0] += i*a[j]; 40 // dp[i][j][1] += i*a[j]; 41 // ans = max(ans, max(dp[i][j][0], dp[i][j][1])); 42 // } 43 // } 44 // printf("************************************************************************ "); 45 // for (int i=1;i<=n;i++) 46 // { 47 // for (int j=1;j<=n;j++) 48 // { 49 // printf("%5d", dp[i][j][0]); 50 // } 51 // printf(" "); 52 // } 53 // for (int i=1;i<=n;i++) 54 // { 55 // for (int j=1;j<=n;j++) 56 // { 57 // printf("%5d", dp[i][j][1]); 58 // } 59 // printf(" "); 60 // } 61 // printf("%d ", ans); 62 // return 0; 63 //}
16、
FatMouse and Cheese
题目大意:nxn的矩阵,从(0 , 0) ~ (n-1 , n-1)每个位置有一个数字,从(0, 0)开始走,每次只能横向或竖向走最多k步,且每次走到的位置数字必须大于上一次走到的。求走完一条路,路上数字和的最大值。
思路:记忆化搜索,dfs+dp。
代码:
1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 using namespace std; 5 const int M = 110; 6 int mp[M][M], n, k, dp[M][M]; 7 int dx[4] = {1, -1, 0, 0}; 8 int dy[4] = {0, 0, 1, -1}; 9 10 int dfs(int x, int y); 11 12 int main() 13 { 14 while (~scanf("%d%d", &n, &k) && n > 0 && k > 0) 15 { 16 memset(dp, 0, sizeof(dp)); 17 for (int i=1;i<=n;i++) 18 for (int j=1;j<=n;j++) 19 scanf("%d", &mp[i][j]); 20 dfs(1, 1); 21 printf("%d ", dp[1][1]); 22 } 23 return 0; 24 } 25 26 int dfs(int x, int y) 27 { 28 if (dp[x][y]) 29 return dp[x][y]; 30 int mx = 0; 31 for (int i=0;i<4;i++) 32 { 33 for (int j=1;j<=k;j++) 34 { 35 int nx = x + dx[i]*j; 36 int ny = y + dy[i]*j; 37 if (nx < 1 || nx > n || ny < 1 || ny > n || mp[nx][ny] <= mp[x][y]) 38 continue; 39 int temp = dfs(nx, ny); 40 mx = max(mx, temp); 41 } 42 } 43 dp[x][y] = mx + mp[x][y]; 44 return dp[x][y]; 45 }
总结:将dp用在dfs中的记忆化搜索,以前没写过。
17、
Phalanx
题目大意:nxn的方阵,求最大对称子方阵的边长。(对称子方阵以副对角线为对称轴)
思路:每个元素向上向右检查是否匹配,若匹配个数大于右上角位置的值,则当前位置的值等于右上角位置的值加一。
状态:dp[i][j]表示以(i, j)为左下角元素的最大对称子方阵的边长。
状态转移方程:满足条件时,dp[i][j] = dp[i-1][j+1] + 1。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 const int M = 1010; 6 char str[M][M]; 7 int n, dp[M][M], ans; 8 9 int main() 10 { 11 while (scanf("%d", &n) && n) 12 { 13 memset(dp, 0, sizeof(dp)); 14 ans = 1; 15 for (int i=0; i<n; i++) 16 scanf("%s", str[i]); 17 for (int i=0; i<n; i++) 18 { 19 for (int j=0; j<n; j++) 20 { 21 dp[i][j] = 1; 22 if (i-1 < 0 || j+1 >= n) 23 continue; 24 int k = 1, cnt = 1; 25 while (i - k >= 0 && j + k < n) 26 { 27 if (str[i-k][j] == str[i][j+k]) 28 cnt++, k++; 29 else 30 break; 31 } 32 if (cnt > dp[i-1][j+1]) 33 dp[i][j] = dp[i-1][j+1] + 1; 34 else 35 dp[i][j] = cnt; 36 ans = max(ans, dp[i][j]); 37 } 38 } 39 printf("%d ", ans); 40 } 41 return 0; 42 }
18、
Milking Time
题目大意:FJ有m个时间段可以挤奶,每个时间段有开始时间和结束时间以及这一段时间内的价值,在完成一个时间段之后,牛必须休息r时间。并且牛必须从每个时间段的开始时间到结束时间一直被压榨。求能压榨的最大价值。
思路:以开始时间排序后,开始dp。
状态:dp[i]表示第i时间段能获得的最大价值。
状态转移方程:dp[i] = max(dp[i], dp[1...i-1]+v[i])。
代码:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 const int M = 1010; 5 struct node{ 6 int s, e, v; 7 }cow[M]; 8 int n, m, r, dp[M]; 9 bool cmp(node a, node b); 10 11 int main() 12 { 13 scanf("%d %d %d", &n, &m, &r); 14 for (int i=1;i<=m;i++) 15 { 16 scanf("%d %d %d", &cow[i].s, &cow[i].e, &cow[i].v); 17 cow[i].e += r; 18 } 19 sort(cow+1, cow+1+m, cmp); 20 int mx = 0; 21 for (int i=1;i<=m;i++) 22 { 23 dp[i] = cow[i].v; 24 for (int j=1;j<=i-1;j++) 25 { 26 if (cow[i].s >= cow[j].e) 27 { 28 dp[i] = max(dp[i], dp[j]+cow[i].v); 29 } 30 } 31 mx = max(mx, dp[i]); 32 } 33 printf("%d ", mx); 34 return 0; 35 } 36 37 bool cmp(node a, node b) 38 { 39 if (a.s == b.s) return a.e < b.e; 40 else return a.s < b.s; 41 }
19、