算法竞赛入门——线性DP

最少拦截系统

Problem Description

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.
怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.

Input

输入若干组数据.每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)

Output

对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统.

Sample Input

8 389 207 155 300 299 170 158 65

Sample Output

2

分析

这道题目实际上是求最长上升子序列(LIS),下面分析如何求最长上升子序列。

首先考虑搜索,枚举所有方案,可得最长的子序列

void dfs(int len,int tail)
{
	if(len>ans) ans=len;
	for(int i=tail+1;i<=n;i++)
	{
		if(a[i]>a[tail])
			dfs(len+1,i);
	}
}
dfs(0,0);

这种做法的时间复杂度是指数级别的,提交结果Time Limit Exceeded

通过以上的搜索代码发现,每一次的决策只与一个上升子序列的最后一个数有关,考虑以每个数结尾的最长子序列的长度作为状态,可得状态转移方程
f [ i ] = m a x ( f [ i ] , f [ j ] + 1 ) ( 0 ⩽ j < i , a [ j ] < a [ i ] ) f[i]=max(f[i],f[j]+1)(0\leqslant j<i,a[j]<a[i]) f[i]=max(f[i],f[j]+1)(0j<i,a[j]<a[i])
时间复杂度O(n2)

AC代码

#include <iostream>
#include <cstring>
using namespace std;

const int N=1e5+10;
int a[N],f[N];
int ans,n;

int main()
{
	while(cin>>n)
	{
		for(int i=1;i<=n;i++) cin>>a[i];
		ans=0;
		memset(f,0,sizeof(f));
		for(int i=1;i<=n;i++)
		{
			for(int j=0;j<i;j++)
			{
				if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);
			}
			ans=max(ans,f[i]);
		}
		cout<<ans<<endl; 
	}
	
	return 0;
}

也可以用贪心+二分做,时间复杂度O(nlog(n))

#include <bits/stdc++.h>

using namespace std;

const int N=1e5+10;
int a[N];
int b[N];
int n,tot;

int main()
{
	while(cin>>n)
	{
		tot=0;
		for(int i=1;i<=n;i++) cin>>a[i];
		b[++tot]=a[1];
		for(int i=2;i<=n;i++)
		{
			if(a[i]>b[tot]) b[++tot]=a[i];
			else
			{
				int c=lower_bound(b+1,b+tot+1,a[i])-b;
				b[c]=a[i];
			}
		}
		cout<<tot<<endl;
	}
	

	return 0;
}

扩展

打印最长上升子序列的方案,用一个pos数组存每个数的前一个数的位置的下标

int pos[N],tail;

void print(int i)
{
	if(i==0) return ;
	print(pos[i]);
	cout<<a[i]<<" ";
}

for(int i=1;i<=n;i++)
{
	for(int j=0;j<i;j++) {
		if(a[i]>a[j]) {
			if(f[j]+1>f[i]) {
				f[i]=f[j]+1;
				pos[i]=j;
			}
		}
	}
	if(f[i]>ans) {
		ans=f[i];
		tail=i;
	}
}
print(tail);

过河卒

题目描述

如图,A 点有一个过河卒,需要走到目标 B 点。卒行走规则:可以向下、或者向右。同时在棋盘上的任一点有一个对方的马(如上图的C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点。例如上图 C 点上的马可以控制 9 个点(图中的P1,P2 … P8 和 C)。卒不能通过对方马的控制点。
在这里插入图片描述

棋盘用坐标表示,A 点(0,0)、B 点(n,m)(n,m 为不超过 20 的整数,并由键盘输入),同样马的位置坐标是需要给出的(约定: C<>A,同时C<>B)。现在要求你计算出卒从 A 点能够到达 B 点的路径的条数。

输入描述:

输入B点的坐标(n,m)以及对方马的坐标(X,Y){不用判错}

输出描述:

输出一个整数(路径的条数)。

示例1

输入

6 6 3 2

输出

17

分析

先不考虑马的控制点对路径条数的影响,设f[i][j]表示(0,0)到(i,j)的路径条数,可得状态转移方程
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − 1 ] f[i][j]=f[i-1][j]+f[i][j-1] f[i][j]=f[i1][j]+f[i][j1]
边界:f[i][0]=1,f[0][j]=1

由于马和马能到达的八个位置不能通过,只需将对应位置的值设为0

AC代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
ll f[30][30];

int n,m,x,y;

bool judge(int x,int y)
{
	return 0<=x&&x<=n&&0<=y&&y<=m;
}

void solve()
{
	if(judge(x,y))     f[x][y]=0;
	if(judge(x-2,y-1)) f[x-2][y-1]=0;
	if(judge(x-1,y-2)) f[x-1][y-2]=0;
	if(judge(x+1,y-2)) f[x+1][y-2]=0;
	if(judge(x+2,y-1)) f[x+2][y-1]=0;
	if(judge(x+2,y+1)) f[x+2][y+1]=0;
	if(judge(x+1,y+2)) f[x+1][y+2]=0;
	if(judge(x-1,y+2)) f[x-1][y+2]=0;
	if(judge(x-2,y+1)) f[x-2][y+1]=0;
}

int main()
{
	memset(f,-1,sizeof(f));
	cin>>n>>m>>x>>y;
	solve();
	if(f[0][0]!=0) f[0][0]=1;
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			if(f[i][j]==-1)
			{
				if(i-1>=0) f[i][j]+=f[i-1][j];
				if(j-1>=0) f[i][j]+=f[i][j-1];
				f[i][j]+=1;
			}
		}
	}
	cout<<f[n][m]<<endl;
	
	return 0;
}

注意

1.为了区分马的控制点和其他点,将f[][]数组初始化为-1,马的控制点值为0
2.如果(0,0)不在马的控制点,对应位置值设为1
3.注意判断i-1,j-1是否大于等于0
4.f[][]数组注意开long long,路径条数可能超过int的范围

传球游戏

题目描述

上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球的方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共2种。

输入描述:

共一行,有两个用空格隔开的整数n,m( 3 ≤ n ≤ 30,1 ≤ m ≤ 30 )。

输出描述:

共一行,有一个整数,表示符合题意的方法数。

示例1

输入

3 3

输出

2

备注:

40%的数据满足:3 ≤ n ≤ 30,1 ≤ m ≤ 20;
100%的数据满足:3 ≤ n ≤ 30,1 ≤ m ≤ 30。

分析

原问题:从1开始传球第m步球回到1的方法数
子问题:从1开始传球第i步球到达j的方法数
状态表示:f[i][j]表示第i次传球之后球在第j个人手上的方法数
状态转移方程:f[i][j] = f[i-1][j-1] + f[i-1][j+1] (j为1时,j-1=n;j为n时,j+1==1)
边界:f[0][1] = 1
目标:f[m][1]

AC代码

#include <bits/stdc++.h>

using namespace std;

const int N=35;
int f[N][N];

int main()
{
	int n,m;
	cin>>n>>m;
	f[0][1]=1;
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n;j++)
		{
			f[i][j]=f[i-1][j==1?n:j-1]+f[i-1][j==n?1:j+1];
		}
	}
	cout<<f[m][1]<<endl;
	
	return 0;
}

花店橱窗

分析

首先使用搜索的方法,枚举所有方案,求最大美观程度的值

int ans=-(1<<30);
int n,m;

void dfs(int len,int res,int tail)
{
	if(len==n)
	{
		ans=max(ans,res);
	}
	if(tail==m) return;
	for(int i=tail+1;i<=m;i++)
	{
		res+=a[len+1][i];
		dfs(len+1,res,i);
		res-=a[len+1][i];
	}
}

cin>>n>>m;
for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
		cin>>a[i][j];
dfs(0,0,0); 
cout<<ans<<endl;

通过分析,可以对暴力的方法进行优化,例如如果第2朵花放在第j个花瓶,要使美观程度最大,第1朵花必然放在1~j-1中美观程度最大的花瓶中,这也说明此问题具有最优子结构,可以用动态规划的方法求解。

状态表示:f[i][j]表示前i朵花放在前j个花瓶获得的最大美观程度
状态转移方程:
f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] ) f[i][j]=max(f[i][j-1],f[i-1][j-1]+a[i][j]) f[i][j]=max(f[i][j1],f[i1][j1]+a[i][j])
由于美观程度可能是负值,f[][]数组的初值为负无穷,还要注意i=1时,f[i][j]的值只与f[i][j-1]和a[i][j]有关,f[i][j]的值不会从第i-1层转移而来,因此i=0时,f[i][j]应赋为0

AC代码

#include <bits/stdc++.h>
using namespace std;

const int N=105;
int a[N][N];
int f[N][N];
int ans[N];
int n,m;

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>a[i][j];
	memset(f,0xcf,sizeof(f));
	for(int i=0;i<=m;i++) f[0][i]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=m;j++)
		{
			f[i][j]=max(f[i][j-1],f[i-1][j-1]+a[i][j]);
		}
	}
	cout<<f[n][m]<<endl;
	int t=m;
	for(int i=n;i>=1;i--)
	{
		while(f[i][t]==f[i][t-1]) t--;
		ans[i]=t;
		t--;
	}
	for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
	
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值