总之就是 | 动态规划杂题

这里会不定期的收集我觉得有必要整理一下的动态规划

已经不再更新了

一本通 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 拦截导弹

题目难度

洛谷P1020

一本通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

我看了很多的解释,感觉都看不懂,于是在这些解释的基础上自己导出得出这样一个解释

如果我这个解释不准确请及时向我提出来!

我不想让自己一直错误解释一个定理,再或者误导别人

参考到的解释:

解释A,我现在的解释就是在这个基础上得来的

解释B,博主看懂了但是我并没看懂

总之这里就可以用这样一个定理来解决问题二

因为最少的 最长下降子序列 的个数就是我们要求的系统数

这里把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

  1. x并不一定通往x到n这个区间里面的每一个城市,所以此时需要判断a(x,i)是否为0

  2. 还有一种特殊的情况是遍历到的这个城市和n之间没有路线,那为了避免这种情况,在这里把所有x的f(x)初始化为一个大数,表示这个城市和n不通

    (原题没有数据范围,还是看的书上的赋值为1000000我才决定的赋值多少)

  3. 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

posted @ 2021-02-22 15:35  HerikoDeltana  阅读(87)  评论(0编辑  收藏  举报