线性DP总结(studying

写在前面

虽然都说线性DP是入门,但我还是今天才开始学

线性DP就是珂以通过线性处理得出答案的一种DP

每一种状态都可以从前面推得,并且推导过程是呈线性的

参考题单(本人现在主要用luogu,所以这些题都是luogu上找的)

下面是例题:

P1057 传球游戏 

这道题算是热身题吧

Solution

思路很简单,

每个人手中的球只能从他左边的同学和右边的同学传过来,所以递推求就好了

我们用i表示编号,j表示第几次传球,f[][]表示有几条到达这种状态的“路”

那么可以推出递推式:

f[1][0] = 1;

f[i][j] = f[i-1][j-1] + f[i+1][j-1];

下面是代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 int n, m;
 5 int f[33][33];
 6 int main()
 7 {
 8     scanf("%d%d", &n, &m);
 9     f[1][0] = 1;
10     for(int j = 1; j <= m; ++j){
11         for(int i = 1; i <= n; ++i){
12             int x, y;
13             if(i - 1 == 0) x = n;
14             else x = i - 1;
15             if(i + 1 == n + 1) y = 1;
16             else y = i + 1;
17             f[i][j] = f[x][j-1] + f[y][j-1];
18         }
19     }
20     printf("%d", f[1][m]);
21     
22     return 0;
23 }
View Code

 

P1233 木棍加工

简述题意

如果下次加工的木棍比上一次的又短又细,就不需要准备时间,否则需要一分钟的准备时间

Solution

先将木棍按长度从大到小排序,在跑一个最长上升子序列即可(不知道为什么按宽度排序跑出来90pts

粘代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 const int MAXN = 5010;
 6 struct Wood{
 7     int l,w;
 8     bool operator < (const Wood &b) const {return l > b.l;}
 9 }wood[MAXN];
10 int n, ans;
11 int f[MAXN];
12 int main()
13 {
14 //    freopen("P1233_8.in", "r", stdin); 
15     scanf("%d", &n);
16     for(int i = 1; i <= n; ++i){
17         scanf("%d%d", &wood[i].l, &wood[i].w);
18     }
19     
20     sort(wood + 1, wood + 1 + n);
21     
22     for(int i = 1; i <= n; ++i){
23         f[i] = 1;
24         for(int j = 1; j <= i; ++j){
25             if(wood[i].w > wood[j].w) f[i] = max(f[i], f[j] + 1);
26         }
27         ans = max (ans, f[i]);
28     //    cout<<ans<<endl;
29     }
30     
31     printf("%d", ans);
32     
33     return 0;
34 }
View Code

 

P1020 导弹拦截

简述题意

现有某导弹拦截系统,每一次拦截的导弹只能小于等于上一次拦截的导弹(当然,第一次珂以拦截任意高度的导弹),给你依次飞来的导弹高度,输出最多能拦截的导弹数和拦截所有导弹最少需要多少套系统

Solution

因为这个系统拦截的导弹高度是单调不升的,所以第一问就是一个最长不上升子序列

第二问珂以根据Dilworth定理推得求一个最长上升子序列

某大佬JJPJJ对于求最长上升子序列的证明:

(1)假设打导弹的方法是这样的:取任意一个导弹,从这个导弹开始将能打的导弹全部打完。而这些导弹全部记为为同一组,再在没打下来的导弹中任选一个重复上述步骤,直到打完所有导弹。

(2)假设我们得到了最小划分的K组导弹,从第a(1<=a<=K)组导弹中任取一个导弹,必定可以从a+1组中找到一个导弹的高度比这个导弹高(因为假如找不到,那么它就是比a+1组中任意一个导更高,在打第a组时应该会把a+1组所有导弹一起打下而不是另归为第a+1组),同样从a+1组到a+2组也是如此。那么就可以从前往后在每一组导弹中找一个更高的连起来,连成一条上升子序列,其长度即为K;

(3)设最长上升子序列长度为P,则有K<=P;又因为最长上升子序列中任意两个不在同一组内(否则不满足单调不升),则有

P>=K,所以K=P。

直接暴力

 

 1 /*
 2 Work by: Suzt_ilymics
 3 Knowledge: 线性DP 
 4 */
 5 #include<iostream>
 6 #include<cstdio>
 7 using namespace std;
 8 const int MAXN = 1e5+4;
 9 int n, ans = -1, cnt = -1;
10 int a[MAXN];
11 int f[MAXN], sum[MAXN];
12 
13 int max(int x, int y){return x > y ? x : y; }
14 
15 int main()
16 {
17     while(cin >> a[++n]);
18     for(int i = 1; i < n; ++i){
19         f[i] = 1;
20         sum[i] = 1;
21         for(int j = 1; j < i; ++j){
22             if(a[j] >= a[i])
23             f[i] = max(f[i], f[j] + 1);
24             if(a[j] < a[i])
25             sum[i] = max(sum[i], sum[j] + 1);
26         }
27         ans = max(ans, f[i]);
28         cnt = max(cnt, sum[i]);
29     }
30     
31     printf("%d\n%d", ans, cnt);
32     
33     return 0;
34 }
O(n^2)代码

 

 

显然会T

所以这里用到了某大佬「已注销」的常数优化方法:

在朴素n^2算法中,用f[i]储存以i结尾的最长不上升子序列长度,如样例

i 1 2 3 4 5 6 7 8

a 389 207 155 300 299 170 158 65

f 1 2 3 2 3 4 5 6

发现当f的值相同时,越后面的导弹高度越高

用d[i]维护f值为i的最后一个导弹的位置,t记录当前已经求出最长不升子序列长度

递推求f时枚举a[d[t]],a[d[t-1]],。。。,a[d[1]]是否≥当前求的导弹高度,是就更新f

 这是优化后代码

 1 /*
 2 Work by: Suzt_ilymics
 3 Knowledge: 线性DP, LIS 
 4 Time: O(??)
 5 */
 6 #include<iostream>
 7 #include<cstdio>
 8 using namespace std;
 9 const int MAXN = 1e5+4;
10 int n, ans = -1, cnt = -1;
11 int a[MAXN], d[MAXN], t;//用d数组维护f值为i的最后一个导弹的位置 
12 int f[MAXN], sum[MAXN];
13 
14 int max(int x, int y){return x > y ? x : y; }
15 
16 int main()
17 {
18     while(cin >> a[++n]);
19     //第一问:求最长不上升子序列 
20     for(int i = 1; i < n; ++i){
21         f[i] = 1;
22         for(int j = ans; j > 0; --j){
23             if(a[i] <= a[d[j]]){
24                 f[i] = f[d[j]] + 1; break;
25             }
26         } 
27         d[f[i]] = i;
28         ans = max(ans, f[i]);
29     }
30     
31     //第二问,求最长上升子序列 
32     for(int i = 1; i < n; ++i){
33         sum[i] = 1;
34         for(int j = cnt; j > 0; --j){
35             if(a[i] > a[d[j]]){
36                 sum[i] = sum[d[j]] + 1; break;
37             }
38         }
39         d[sum[i]] = i;
40         cnt = max(cnt, sum[i]);
41     }
42     
43     printf("%d\n%d", ans, cnt);
44     
45     return 0;
46 }
O(??)代码

 本人做完这个题突发灵感,写了一道题目这里,希望有大佬能提出具体思路帮我完善题面

posted @ 2020-10-16 22:00  Suzt_ilymtics  阅读(149)  评论(0编辑  收藏  举报