线性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 }
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 }
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 }
显然会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 }
本人做完这个题突发灵感,写了一道题目这里,希望有大佬能提出具体思路帮我完善题面