总之就是 | 动态规划杂题
这里会不定期的收集我觉得有必要整理一下的动态规划题
已经不再更新了
一本通 T1258 数字金字塔
这题洛谷也有:洛谷P1216
题目难度
普及-
收录原因
真正意义上做的第一道DP题(?)
题目分析
这是输入的格式
13 11 8 12 7 26 6 14 15 8 12 7 13 24 11
这个很好划分阶段,一行一个就行
这里令f(x,y)(x是行,y是列)表示顶点到点(x,y)和的最大值
a(x,y)是点(x,y)的数值
其中f(1,1)=a(1,1),这就是状态转换的边界
本题我采取的是从最顶层推到最底层的方法
那么先来分析样例
f(1,1)刚才已经说到了是a(1,1)
f(1,1)的来源有一个:f(1,1)+a(1,1)
f(1,2)的来源有一个:f(1,1)+a(1,2)
根据这两个还看不出来什么,继续到下一阶段
f(2,1)的来源有一个:f(1,1)+a(2,1)
f(2,2)的来源有两个:f(1,2)+a(2,2)或f(1,1)+a(2,2)
到这里就能发现转移方程的影(嘤)子
题目让求最大的和,那这里我们在这连个里面取一个最大的就好
f(2,2)就等于max(f(1,2),f(1,1))+a(2,2)
转为一般的f(x,y),就可以得到状态转移方程
(好啰嗦啊,实际上很好想啊喂)
f(x,y)=max(f(x-1,y),f(x-1,y-1))+a(x,y)
剩下的就不必多说了,基操
代码实现
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n,a[1001][1001],f[1001][1001],sum;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int u=1;u<=i;u++)
{
cin>>a[i][u];
}
}
f[1][1]=a[1][1];
for(int i=2;i<=n;i++)
{
for(int u=1;u<=i;u++)
{
f[i][u]=max(f[i-1][u-1],f[i-1][u])+a[i][u];
}
}
for(int i=1;i<=n;i++)
{
sum=max(sum,f[n][i]);
}
printf("%d",sum);
return 0;
}
本题总结
说实话没啥好总结的
这种入门的DP题就记住我在新知初探:动态规划里面说的基本思路就能做
一本通 T1259 最长不下降序列
题目难度
洛谷没有,差不多就普及--
收录原因
初学DP,多整理整理思路
才不是因为做的题太少了
题目分析
这是题目给的输入
14//长度n 13 7 9 16 38 24 37 18 44 19 21 22 63 15
这是题目给的输出
max=8 7 9 16 18 19 21 22 63
由于这已经是第二道题了
就不说那么细节了
才不是因为懒
一看题就发现我们需要一个f(x)来表示从x到n(n是长度)的最长的不下降序列长度
但是再往下一看还需要我们输出这个序列,那我们就需要记录下来这个序列的每一个数的位置最后输出
于是乎刚才的一维数组f就UPD成了二维数组
既然已经成了二维数组,本着不用白不用(懒得再打一个一维数组)的原则
这里把每个x对应的数字也归入这个f里面
那么
f(x,1)代表x对应的数字
f(x,2)代表从x到n的最长的不下降序列的长度
f(x,3)代表x所在的最长的不下降序列的下一个数
那么我们就可以愉快地写代码了
状态转移方程还没推呢啊
但是也很简单,从x到n的区间里面找到序列长度最大一个就行了
f(x,2)=1+maxx
那么我们就可以愉快地写代码了
代码实现
using namespace std;
int f[201][3],n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>f[i][1];
f[i][2]=1;
f[i][3]=0;
}
for(int i=n-1;i>=1;i--)
{
for(int u=i+1;u<=n;u++)
{
if((f[i][1]<=f[u][1])&&(f[i][2]<f[u][2]+1))
{
f[i][2]=f[u][2]+1;
f[i][3]=u;
}
}
}
int k=0,h=0;
for(int i=1;i<=n;i++)
{
if(f[i][2]>h)
{
h=f[i][2];
k=i;
}
}
cout<<"max="<<h<<endl;
cout<<' '<<f[k][1];
k=f[k][3];
while(k!=0)
{
cout<<" "<<f[k][1];
k=f[k][3];
}
return 0;
}
本题总结
这个就真没啥好总结的了......
一本通 T1260 拦截导弹
题目难度
这个题在洛谷上是普及/提高-
但是这题在洛谷上面要求的是n^2能拿100
nlogn才能拿200
所以可能这个题真实难度是普及-和普及/提高-的叠加态
收录原因
这个题收录的主要原因是在于
本题分为两问
第一问是要问能拦截的导弹数
这个还好,是最长的不上升子序列
而第二问要的是问至少需要多少套系统
这个我最一开始不会,后来看了题解发现有个叫Dilworth定理的东西
而本题再输入这个地方还有一个点
要求输入一个未知长度的数组
于是决定把这个题整理下来
题目分析
还是从分析样例入手
题目给的输入:
389 207 155 300 299 170 158 65
题目给的输出:
6 2
在这里我的想法是从后往前推
用n来表示数组的长度
用f(i)来表示在第i个数到第n个数的最大的不上升序列的长度
边界值为f(n)=1,f(i)的确定只需要从i+1到n中找出最大的长度再+1即可
(说实话,这道题我做了很长时间,貌似在洛谷上用逆推的大佬比较少,导致我第二问没做出来我也没去看大佬的题解)
第二问呢,要用到Dilworth定理
这个定理的官方解释可以自行度娘,反正我是没看懂
我个人的理解就是:
把一个数列划分成最少的 最长下降子序列 的数目就等于这个数列的最长上升子序列 的长度
举个例子:
如数列 1 2 2 3 2 3
最长下降子序列可以分成:1,2,2,3 2,3 ——5个
最长上升的子序列就是:1 2 2 3 3 ——长度为5
我看了很多的解释,感觉都看不懂,于是在这些解释的基础上自己导出得出这样一个解释
如果我这个解释不准确请及时向我提出来!
我不想让自己一直错误解释一个定理,再或者误导别人
参考到的解释:
总之这里就可以用这样一个定理来解决问题二
因为最少的 最长下降子序列 的个数就是我们要求的系统数
这里把f(i)重新用了一遍,换一个g(i)什么的也不影响
分析完这些我们就可以上代码了
代码实现
这一个是我写的,和我上面说的思路一样
#include <iostream>
#include <cstdio>
int max(int x,int y)
{
if(x>y)
return x;
else
return y;
}
int main()
{
int a=0, n=1, b=0,f[100001],maxu=0,maxn=0,maxux=0,h=0,maxnx=0,t=1;
//这里的maxux,maxnx等是之前的多余变量,我没有删除
//在下文中注释中的输出都是测试使用的
int l[100001]={0};
while(scanf("%d",&b)==1)
{
l[n]=b;
n++;
if ('\n'==getchar())
{
l[n] = '\0';
break;
}
}
n--;
for(int i=1;i<=n;i++)
{
f[i]=1;
}
for(int i=n-1;i>0;i--)
{
for(int u=i+1;u<=n;u++)
{
if(l[u]<=l[i])
{
if(maxu<f[u])
{
maxu=f[u];
maxux=u;
}
if(maxu==f[u]&&l[maxux]<l[u])
{
maxu=f[u];
maxux=u;
}
}
}
f[i]=maxu+f[i];
maxu=0;
maxux=0;
}
for(int i=1;i<=n;i++)
{
if(maxn<f[i])
{
maxn=f[i];
maxnx=i;
}
//printf("T%d:%d\n",i,f[i][1]);
}
printf("%d\n",maxn);
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(l[j]<l[i])
f[i]=max(f[i],f[j]+1);
}
h=max(h,f[i]);
}
printf("%d\n",h);
/*
for(int i=1; i<=n; i++)
{
printf("%d ",l[i]);
}
printf("\n");*/
return 0;
}
下面这个是洛谷上面的一个题解,和我的第二问解法一致,第一问不同
他的题解链接我也放在这里:一个大佬的洛谷P1020的题解
#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,h,f[100001],a[100001];
int main(){
while(scanf("%d",&a[++n])!=EOF);
n--;
for(int i=n;i>=1;i--)
{
f[i]=1;
for(int j=i+1;j<=n;j++)
{
if(a[j]<=a[i])
f[i]=max(f[i],f[j]+1);
}
k=max(k,f[i]);
}
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1);
}
h=max(h,f[i]);
}
printf("%d\n%d\n",k,h);
}
本题总结
阿这题,要素太多了(QnQ)
一本通 T1261 城市道路交通网
题目难度
在洛谷上面莫得,我个人认为属于普及-和普及/提高-之间
收录原因
这个题让我回想起了之前只会个数组啥的就去莽的NOi Online的一道题
当时那个红题都做不出来的水平居然还能莽对所有样例,可惜那个时候连文件输入输出都不会,要不然就能看看那个时候能得多少分了,相比之下我现在好颓
上面全都划掉,估计也没几分
当然这两个题在做法上没啥关系,只是因为和城市有瓜我才无端联想
题目分析
这个题还是先看样例
输入:
10 0 2 5 1 0 0 0 0 0 0 0 0 0 0 12 14 0 0 0 0 0 0 0 0 6 10 4 0 0 0 0 0 0 0 13 12 11 0 0 0 0 0 0 0 0 0 0 3 9 0 0 0 0 0 0 0 0 6 5 0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0
输出:
minlong=19 1 3 5 8 10
好长啊,但是很好分析
我们在这里要用一个f(x)来表示x到n的最短路长度,n是城市数
a(x,y)来储存输入的东西,这个表示从x到y的距离
c(x)用来记录最短路径上的x的下一个城市的编号
因为在本题中没有编号大的城市到编号小的城市的路径
所以f(x)的确定就只需要找到x到n(包括n,不包括x)之间的到n路径最短的城市编号
但是这道题有如下需要考虑的情况
在这里我把遍历到的数设为i
-
x并不一定通往x到n这个区间里面的每一个城市,所以此时需要判断a(x,i)是否为0
-
还有一种特殊的情况是遍历到的这个城市和n之间没有路线,那为了避免这种情况,在这里把所有x的f(x)初始化为一个大数,表示这个城市和n不通
(原题没有数据范围,还是看的书上的赋值为1000000我才决定的赋值多少) -
f(x)本身比当前遍历到的a(x,i)+f(i)小
这种情况就仍然让f(x)保持原数,以为此事就是当前最短了
分析完这些就可以上代码了
代码实现
#include <iostream>
using namespace std;
long long a[101][101],f[101],c[101],n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int u=1;u<=n;u++)
{
cin>>a[i][u];
}
}
for(int i=1;i<=n;i++)
{
f[i]=1145144;//别问为什么是1145144,问就是去掉最后一个4
}
f[n]=0;
for(int i=n-1;i>=1;i--)
{
for(int u=i+1;u<=n;u++)
{
if((a[i][u]>0)&&(f[u]!=1145144)&&(f[i]>a[i][u]+f[u]))
{
f[i]=f[u]+a[i][u];
c[i]=u;
}
}
}
cout<<"minlong="<<f[1]<<endl;
int x=1;
while(x)
{
cout<<x<<" ";
x=c[x];
}
cout<<endl;
return 0;
}
这个代码好臭啊
本题总结
这个题要考虑一些特殊的情况,所以分析题目的时候要仔细一些
End
更新于2021.2.22