Poki的学习笔记:动态规划
- 洛谷P1020 导弹拦截 线性DP-LIS模型最长上升子序列
- 洛谷P1091 合唱队形-LIS改
- 洛谷P1280 尼克的任务 线段覆盖
- 洛谷P1880 石子合并 区间dp
- 洛谷P1140 相似基因 线性DP
- 洛谷P1282 多米诺骨牌
- 洛谷P1005 矩阵取数游戏
- 洛谷P1373 小a和uim之大逃离
- 洛谷P2279 [HNOI2003]消防局的设立
- 洛谷P1220 关路灯
- 洛谷P1156 垃圾陷阱
- 洛谷P1273 有线电视网
- 洛谷P1169 [ZJOI2007]棋盘制作
- 洛谷P2577 [ZJOI2005]午餐
- 洛谷P1070 道路游戏
- 洛谷P2051 [AHOI2009]中国象棋
- 洛谷P1415 拆分数列
- 洛谷P2157 [SDOI2009]学校食堂
- 洛谷P2216 [HAOI2007]理想的正方形
- 洛谷P2331 [SCOI2005]最大子矩阵
- 洛谷P2467 [SDOI2010]地精部落
- 洛谷P3084 [USACO13OPEN]照片Photo
- 洛谷P2051 [AHOI2009]中国象棋
- 洛谷P1879 [USACO06NOV]玉米田Corn Fields
- 洛谷P1850 换教室
- 洛谷P2831 愤怒的小鸟
- 洛谷P1131 [ZJOI2007]时态同步
- 洛谷P1169 [ZJOI2007]棋盘制作
- 洛谷P3648 [APIO2014]序列分割
- 洛谷P2519 [HAOI2011]problem a
- 例题
- 题集
被牛客多校血虐,发现dp极度薄弱(好其实其他也很薄弱),有想法也不会写,是时候补坑了:-)
洛谷P1020 导弹拦截 线性DP-LIS模型最长上升子序列
https://www.luogu.org/problem/P1020
题意:
维护一个非升的序列和一个单调升序的序列
思路:动态规划,利用f[i]f[i]表示到第ii个导弹时所拦截的总导弹数,则有
f[i]=max(f[i],f[j]+1)i>j&&a[i]≤a[j]
对于第二问,“拦截所有导弹最少要配备多少套这种导弹拦截系统”,利用序列的不下降子序列最少划分数等于上升序列的总长度这一原理(Dilworth定理),即是求最长上升子序列的长度。
f2[i]=max(f2[i],f2[j]+1)i>j&&a[i]>a[j]
时间复杂度O(n2),常规能过一半数据,但是进行一个小优化就可以达到200分!
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
a | 389 | 207 | 155 | 300 | 299 | 170 | 158 | 65 |
dp | 1 | 2 | 3 | 2 | 3 | 4 | 5 | 6 |
常规dp用dp数组记录以i结尾的最长不上升子序列。注意到,如果dp相等那么后面出现的高度一定比前面的大,因为如果后面的小就直接接在前面那个后面接上了。所以利用这层大小关系,我们可以贪心:对于一个dp值只与最后的那个比较。
P.S.此题另一个过200分的做法是贪心二分,在这里就不做了^^~复杂度为O(nlogn)
代码(按了o2优化才过。。不然就超时了)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int a[100005],dp[100005],f[100005];//f用来记前面相对最后相同dp值的坐标
int main()
{
int i,j,ans=0,n=0;
while(~scanf("%d",&a[++n]));
n--;//最后一个是EOF
for(i=1;i<=n;i++)
{
dp[i]=1;
for(j=ans;j>0;j--)
{
if(a[i]<=a[f[j]])
{
dp[i]=dp[f[j]]+1;
break;
}
}
ans=max(ans,dp[i]);
f[dp[i]]=i;
}
printf("%d\n",ans);
ans=0;
for(i=1;i<=n;i++)
{
dp[i]=1;
for(j=ans;j>0;j--)
{
if(a[i]>a[f[j]])//跟上面同理的,只是非增变升所以只有这里变了符号
{
dp[i]=dp[f[j]]+1;
break;
}
}
ans=max(ans,dp[i]);
f[dp[i]]=i;
}
printf("%d\n",ans);
return 0;
}
洛谷P1091 合唱队形-LIS改
https://www.luogu.org/problem/P1091
因为不用太在意时间复杂度,感觉比上面那题还常规。
用dp[]记最长单升子序列,再用dp2[]做一次最长单降子序列,顺便枚举i找到最大值答案即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int a[105],dp[105],dp2[105];
int main()
{
int i,j,ans=0,n;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i]=1;
dp2[i]=1;
}
for(i=1;i<=n;i++)
{
for(j=1;j<i;j++)
{
if(a[i]>a[j])
{
dp[i]=