Part 4.1 线性动态规划

线性动态规划,即具有线性阶段划分的动态规划。

[USACO1.5] [IOI1994]数字三角形 Number Triangles

题目描述

观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

在上面的样例中,从 7 → 3 → 8 → 7 → 5 7 \to 3 \to 8 \to 7 \to 5 73875 的路径产生了最大权值。

输入格式

第一个行一个正整数 r r r ,表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。

输出格式

单独的一行,包含那个可能得到的最大的和。

样例 #1

样例输入 #1

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

样例输出 #1

30

提示

【数据范围】
对于 100 % 100\% 100% 的数据, 1 ≤ r ≤ 1000 1\le r \le 1000 1r1000,所有输入在 [ 0 , 100 ] [0,100] [0,100] 范围内。

题目翻译来自NOCOW。

USACO Training Section 1.5

IOI1994 Day1T1

代码实现

#include<iostream>
using namespace std;
#define MAX_N 1000
int san[MAX_N+5][MAX_N+5];
int dp[MAX_N+5][MAX_N+5];
int n;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		cin>>san[i][j];
	}
	for(int i=1;i<=n;i++)
	dp[n][i]=san[n][i];
	for(int i=n-1;i>=1;i--)
	{
		for(int j=1;j<=i;j++)
		dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+san[i][j];
	}
	cout<<dp[1][1];
	return 0;
 } 

[NOIP2004 提高组] 合唱队形

题目描述

n n n 位同学站成一排,音乐老师要请其中的 n − k n-k nk 位同学出列,使得剩下的 k k k 位同学排成合唱队形。

合唱队形是指这样的一种队形:设 k k k 位同学从左到右依次编号为 1 , 2 , 1,2, 1,2, , k ,k ,k,他们的身高分别为 t 1 , t 2 , t_1,t_2, t1,t2, , t k ,t_k ,tk,则他们的身高满足 t 1 < ⋯ < t i > t i + 1 > t_1< \cdots <t_i>t_{i+1}> t1<<ti>ti+1> > t k ( 1 ≤ i ≤ k ) >t_k(1\le i\le k) >tk(1ik)

你的任务是,已知所有 n n n 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式

共二行。

第一行是一个整数 n n n 2 ≤ n ≤ 100 2\le n\le100 2n100),表示同学的总数。

第二行有 n n n 个整数,用空格分隔,第 i i i 个整数 t i t_i ti 130 ≤ t i ≤ 230 130\le t_i\le230 130ti230)是第 i i i 位同学的身高(厘米)。

输出格式

一个整数,最少需要几位同学出列。

样例 #1

样例输入 #1

8
186 186 150 200 160 130 197 220

样例输出 #1

4

提示

对于 50 % 50\% 50% 的数据,保证有 n ≤ 20 n \le 20 n20

对于全部的数据,保证有 n ≤ 100 n \le 100 n100

代码实现

#include<iostream>
using namespace std;
#define MAX_N 100
int pe[MAX_N+5];
int dp1[MAX_N+5],dp2[MAX_N+5];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>pe[i];
	int temp=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			dp1[j]=1;
			for(int k=1;k<j;k++)
			{
				if(pe[j]<=pe[k])continue;
				dp1[j]=max(dp1[j],dp1[k]+1);
			}	
		}
		for(int j=n;j>=i;j--)
		{
			dp2[j]=1;
			for(int k=n;k>j;k--)
			{
				if(pe[j]<=pe[k])continue;
				dp2[j]=max(dp2[j],dp2[k]+1);
			}	
		}
		temp=max(temp,dp1[i]+dp2[i]);
	}
	cout<<n-temp+1;
	return 0;
}

[NOIP2007 普及组] 守望者的逃离

题目背景

NOIP2007 普及组 T3

题目描述

恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变。

守望者在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上。

为了杀死守望者,尤迪安开始对这个荒岛施咒,这座岛很快就会沉下去。到那时,岛上的所有人都会遇难。

守望者的跑步速度为 17 m / s 17m/s 17m/s,以这样的速度是无法逃离荒岛的。庆幸的是守望者拥有闪烁法术,可在 1 s 1s 1s 内移动 60 m 60m 60m,不过每次使用闪烁法术都会消耗魔法值 10 10 10 点。守望者的魔法值恢复的速度为 4 4 4 点每秒,只有处在原地休息状态时才能恢复。

现在已知守望者的魔法初值 M M M,他所在的初始位置与岛的出口之间的距离 S S S,岛沉没的时间 T T T。你的任务是写一个程序帮助守望者计算如何在最短的时间内逃离荒岛,若不能逃出,则输出守望者在剩下的时间内能走的最远距离。

注意:守望者跑步、闪烁或休息活动均以秒为单位,且每次活动的持续时间为整数秒。距离的单位为米。

输入格式

输入数据共一行三个非负整数,分别表示 M M M S S S T T T

输出格式

输出数据共两行。

第一行一个字符串 Yes \texttt{Yes} Yes No \texttt{No} No,即守望者是否能逃离荒岛。

第二行包含一个整数。第一行为 Yes \texttt{Yes} Yes 时表示守望者逃离荒岛的最短时间;第一行为 No \texttt{No} No 时表示守望者能走的最远距离。

样例 #1

样例输入 #1

39 200 4

样例输出 #1

No
197

样例 #2

样例输入 #2

36 255 10

样例输出 #2

Yes
6

提示

对于 30 % 30\% 30% 的数据, 1 ≤ T ≤ 10 1 \le T \le 10 1T10,$ 1 \le S \le 100$;

对于 50 % 50\% 50% 的数据, 1 ≤ T ≤ 1 0 3 1 \le T \le 10^3 1T103,$ 1 \le S \le 10^4$;

对于 100 % 100\% 100% 的数据, 1 ≤ T ≤ 3 × 1 0 5 1 \le T \le 3\times 10^5 1T3×105 0 ≤ M ≤ 1 0 3 0 \le M \le 10^3 0M103,$ 1 \le S \le 10^8$。

代码实现

#include<iostream>
using namespace std;
int dp[15][2];
int M,S,T;
int main()
{
	cin>>M>>S>>T;
	int n1=S/60+((S%60)!=0);
	int n2=M/10;
	if(n2>=n1){
		if(n1<=T)cout<<"Yes"<<endl<<n1;
		else cout<<"No"<<endl<<60*T;
		return 0;
	}
	else{
		if(n2>T)
		{
			cout<<"No"<<endl<<60*T;
			return 0;
		}
		M-=n2*10;
		for(int i=1;i<=13;i++)dp[i][n2%2]=-0x7ffffff;
		dp[M][n2%2]=60*n2;
	}
	int fail_ans=0;
	for(int j=n2+1;j<=T;j++)
	{
		for(int i=0;i<=13;i++)
		{
			if(i<=3)dp[i][j%2]=max(dp[i][j%2],dp[i+10][1-j%2]+60);
			else dp[i][j%2]=max(dp[i][j%2],dp[i-4][1-j%2]);
			dp[i][j%2]=max(dp[i][j%2],dp[i][1-j%2]+17);
			if(dp[i][j%2]==0)
			dp[i][j%2]=-0x7ffffff;
			if(dp[i][j%2]>=S)
			{
				cout<<"Yes"<<endl<<j<<endl;
				return 0;
			}
			fail_ans=max(fail_ans,dp[i][j%2]);
		}
	}
	cout<<"No"<<endl<<fail_ans<<endl;
	return 0;
}

[NOIP2010 提高组] 乌龟棋

题目背景

NOIP2010 提高组 T2

题目描述

小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。

乌龟棋的棋盘是一行 N N N 个格子,每个格子上一个分数(非负整数)。棋盘第 1 1 1 格是唯一的起点,第 N N N 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。

乌龟棋中 M M M 张爬行卡片,分成 4 4 4 种不同的类型( M M M 张卡片中不一定包含所有 4 4 4 种类型的卡片,见样例),每种类型的卡片上分别标有 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。

游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。

很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。

现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?

输入格式

每行中两个数之间用一个空格隔开。

1 1 1 2 2 2 个正整数 N , M N,M N,M,分别表示棋盘格子数和爬行卡片数。

2 2 2 N N N 个非负整数, a 1 , a 2 , … , a N a_1,a_2,…,a_N a1,a2,,aN,其中 a i a_i ai 表示棋盘第 i i i 个格子上的分数。

3 3 3 M M M 个整数, b 1 , b 2 , … , b M b_1,b_2,…,b_M b1,b2,,bM,表示 M M M 张爬行卡片上的数字。

输入数据保证到达终点时刚好用光 M M M 张爬行卡片。

输出格式

一个整数,表示小明最多能得到的分数。

样例 #1

样例输入 #1

9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1

样例输出 #1

73

提示

每个测试点 1s。

小明使用爬行卡片顺序为 1 , 1 , 3 , 1 , 2 1,1,3,1,2 1,1,3,1,2,得到的分数为 6 + 10 + 14 + 8 + 18 + 17 = 73 6+10+14+8+18+17=73 6+10+14+8+18+17=73。注意,由于起点是 1 1 1,所以自动获得第 1 1 1 格的分数 6 6 6

对于 30 % 30\% 30% 的数据有 1 ≤ N ≤ 30 , 1 ≤ M ≤ 12 1≤N≤30,1≤M≤12 1N30,1M12

对于 50 % 50\% 50% 的数据有 1 ≤ N ≤ 120 , 1 ≤ M ≤ 50 1≤N≤120,1≤M≤50 1N120,1M50,且 4 4 4 种爬行卡片,每种卡片的张数不会超过 20 20 20

对于 100 % 100\% 100% 的数据有 1 ≤ N ≤ 350 , 1 ≤ M ≤ 120 1≤N≤350,1≤M≤120 1N350,1M120,且 4 4 4 种爬行卡片,每种卡片的张数不会超过 40 40 40 0 ≤ a i ≤ 100 , 1 ≤ i ≤ N , 1 ≤ b i ≤ 4 , 1 ≤ i ≤ M 0≤a_i≤100,1≤i≤N,1≤b_i≤4,1≤i≤M 0ai100,1iN,1bi4,1iM

代码实现

#include<iostream>
using namespace std;
#define MAX_N 350
#define MAX_M 120
int a[MAX_N+5];
int kind[5]={0};
int dp[50][50][50]={0};
int main()
{
	int N,M;
	cin>>N>>M;
	for(int i=0;i<N;i++)
	cin>>a[i];
	for(int i=1,x;i<=M;i++)
	{
		cin>>x;
		kind[x]++;	
	}
	dp[0][0][0]=a[0];
	for(int i=0;i<=kind[1];i++)
	{
		for(int j=0;j<=kind[2];j++)
		{
			for(int k=0;k<=kind[3];k++)
			{
				for(int l=0;l<=kind[4];l++)
				{
					int ans=0;
					int s=i+2*j+3*k+4*l;
					if(i)ans=max(ans,dp[j][k][l]);
					if(j)ans=max(ans,dp[j-1][k][l]);
					if(k)ans=max(ans,dp[j][k-1][l]);
					if(l)ans=max(ans,dp[j][k][l-1]);
					dp[j][k][l]=ans+a[s];
				}
			}
		}
	}
	cout<<dp[kind[2]][kind[3]][kind[4]];
	return 0;
 } 

饥饿的奶牛

题目描述

有一条奶牛冲出了围栏,来到了一处圣地(对于奶牛来说),上面用牛语写着一段文字。

现用汉语翻译为:

N N N 个区间,每个区间 x , y x,y x,y 表示提供的 x ∼ y x\sim y xy y − x + 1 y-x+1 yx+1 堆优质牧草。你可以选择任意区间但不能有重复的部分。

对于奶牛来说,自然是吃的越多越好,然而奶牛智商有限,现在请你帮助他。

输入格式

第一行一个整数 N N N

接下来 N N N 行,每行两个数 x , y x,y x,y,描述一个区间。

输出格式

输出最多能吃到的牧草堆数。

样例 #1

样例输入 #1

3
1 3
7 8
3 4

样例输出 #1

5

提示

1 ≤ n ≤ 1.5 × 1 0 5 1 \leq n \leq 1.5 \times 10^5 1n1.5×105 0 ≤ x ≤ y ≤ 3 × 1 0 6 0 \leq x \leq y \leq 3 \times 10^6 0xy3×106

代码实现

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_N 150000
int n;
struct Node{
	int l,r;
}grass[MAX_N+5];
int dp[MAX_N+5];
bool cmp(Node a,Node b)
{
	return a.r<b.r;
}
int binary_search(int l,int r,int key)
{
	int mid;
	while(l<r)
	{
		mid=(l+r+1)>>1;
		if(grass[mid].r>=key)r=mid-1;
		else l=mid;
	}
	return l;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>grass[i].l>>grass[i].r;
	sort(grass+1,grass+1+n,cmp);
	for(int i=1;i<=n;i++)
	{
		dp[i]=max(dp[i-1],dp[binary_search(0,i-1,grass[i].l)]+grass[i].r-grass[i].l+1);
	}
	cout<<dp[n];
	return 0;
	
}

[NOIP2015 提高组] 子串

题目背景

NOIP2015 Day2T2

题目描述

有两个仅包含小写英文字母的字符串 A A A B B B

现在要从字符串 A A A 中取出 k k k 个互不重叠的非空子串,然后把这 k k k 个子串按照其在字符串 A A A 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 B B B 相等?

注意:子串取出的位置不同也认为是不同的方案。

输入格式

第一行是三个正整数 n , m , k n,m,k n,m,k,分别表示字符串 A A A 的长度,字符串 B B B 的长度,以及问题描述中所提到的 k k k,每两个整数之间用一个空格隔开。

第二行包含一个长度为 n n n 的字符串,表示字符串 A A A

第三行包含一个长度为 m m m 的字符串,表示字符串 B B B

输出格式

一个整数,表示所求方案数。

由于答案可能很大,所以这里要求输出答案对 1000000007 1000000007 1000000007 取模的结果。

样例 #1

样例输入 #1

6 3 1 
aabaab 
aab

样例输出 #1

2

样例 #2

样例输入 #2

6 3 2 
aabaab 
aab

样例输出 #2

7

样例 #3

样例输入 #3

6 3 3 
aabaab 
aab

样例输出 #3

7

提示

样例解释

所有合法方案如下:(加下划线的部分表示取出的字串)

样例 1: aab ‾   aab,aab   aab ‾ \texttt{\underline{aab}\,aab,aab\,\underline{aab}} aabaab,aabaab
样例 2: a ‾   ab ‾   aab, a ‾   aba   ab ‾ ,a   a ‾   ba   ab ‾ ,aab   a ‾   ab ‾ , aa ‾   b ‾   aab, aa ‾   baa   b ‾ ,aab   aa ‾   b ‾ \texttt{\underline{a}\,\underline{ab}\,aab,\underline{a}\,aba\,\underline{ab},a\,\underline{a}\,ba\,\underline{ab},aab\,\underline{a}\,\underline{ab},\underline{aa}\,\underline{b}\,aab,\underline{aa}\,baa\,\underline{b},aab\,\underline{aa}\,\underline{b}} aabaab,aabaab,aabaab,aabaab,aabaab,aabaab,aabaab
样例 3: a ‾   a ‾   b ‾   aab, a ‾   a ‾   baa   b ‾ , a ‾   ab   a ‾   a   b ‾ , a ‾   aba   a ‾   b ‾ ,a   a ‾   b   a ‾   a   b ‾ ,a   a ‾   ba   a ‾   b ‾ ,aab   a ‾   a ‾   b ‾ \texttt{\underline{a}\,\underline{a}\,\underline{b}\,aab,\underline{a}\,\underline{a}\,baa\,\underline{b},\underline{a}\,ab\,\underline{a}\,a\,\underline{b},\underline{a}\,aba\,\underline{a}\,\underline{b},a\,\underline{a}\,b\,\underline{a}\,a\,\underline{b},a\,\underline{a}\,ba\,\underline{a}\,\underline{b},aab\,\underline{a}\,\underline{a}\,\underline{b}} aabaab,aabaab,aabaab,aabaab,aabaab,aabaab,aabaab

数据范围

对于第 1 组数据: 1 ≤ n ≤ 500 , 1 ≤ m ≤ 50 , k = 1 1≤n≤500,1≤m≤50,k=1 1n500,1m50,k=1;
对于第 2 组至第 3 组数据: 1 ≤ n ≤ 500 , 1 ≤ m ≤ 50 , k = 2 1≤n≤500,1≤m≤50,k=2 1n500,1m50,k=2;
对于第 4 组至第 5 组数据: 1 ≤ n ≤ 500 , 1 ≤ m ≤ 50 , k = m 1≤n≤500,1≤m≤50,k=m 1n500,1m50,k=m;
对于第 1 组至第 7 组数据: 1 ≤ n ≤ 500 , 1 ≤ m ≤ 50 , 1 ≤ k ≤ m 1≤n≤500,1≤m≤50,1≤k≤m 1n500,1m50,1km;
对于第 1 组至第 9 组数据: 1 ≤ n ≤ 1000 , 1 ≤ m ≤ 100 , 1 ≤ k ≤ m 1≤n≤1000,1≤m≤100,1≤k≤m 1n1000,1m100,1km;
对于所有 10 组数据: 1 ≤ n ≤ 1000 , 1 ≤ m ≤ 200 , 1 ≤ k ≤ m 1≤n≤1000,1≤m≤200,1≤k≤m 1n1000,1m200,1km

代码实现

#include<iostream>
#include<cstring>
using namespace std;
#define MOD 1000000007
int n,m,l;
char a[1005],b[1005];
int dp[2][205][205];
int sum[2][205][205];
int main()
{
	cin>>n>>m>>l;
	scanf("%s %s",a+1,b+1);
	dp[0][0][0]=dp[1][0][0]=1;
	int now=0;
	for(int i=1;i<=n;i++,now^=1)
	{
		for(int j=1;j<=m;j++)
		{
			for(int k=1;k<=l;k++)
			{
				dp[now][j][k]=dp[now^1][j][k];
				if(a[i]==b[j])
				{
					sum[now][j][k-1]=(sum[now^1][j-1][k-1]+dp[now^1][j-1][k-1])%MOD;
				}
				else sum[now][j][k-1]=0;
				dp[now][j][k]+=sum[now][j][k-1];
				dp[now][j][k]%=MOD;
			}
		}
	}
	cout<<dp[now^1][m][l];
	return 0;
}

在这里插入图片描述

[SCOI2009] 粉刷匠

题目描述

windy 有 N N N 条木板需要被粉刷。 每条木板被分为 M M M 个格子。 每个格子要被刷成红色或蓝色。

windy 每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。 每个格子最多只能被粉刷一次。

如果 windy 只能粉刷 T T T 次,他最多能正确粉刷多少格子?

一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。

输入格式

第一行包含三个整数, N , M , T N,M,T N,M,T

接下来有 N N N 行,每行一个长度为 M M M 的字符串,0 表示红色,1 表示蓝色。

输出格式

包含一个整数,最多能正确粉刷的格子数。

样例 #1

样例输入 #1

3 6 3
111111
000000
001100

样例输出 #1

16

提示

30 % 30\% 30% 的数据,满足 1 ≤ N , M ≤ 10 , 0 ≤ T ≤ 100 1 \le N,M \le 10,0 \le T \le 100 1N,M10,0T100

100 % 100\% 100% 的数据,满足 1 ≤ N , M ≤ 50 , 0 ≤ T ≤ 2500 1 \le N,M \le 50,0 \le T \le 2500 1N,M50,0T2500

代码实现

#include<iostream>
using namespace std;
#define MAX_N 50
#define MAX_T 2500
int dp[MAX_N+5][MAX_T+5];
char val[MAX_N+5][MAX_N+5];
int g[MAX_N+5][MAX_N+5][MAX_T+5];
int sum[MAX_N+5][MAX_N+5];
int n,m,t;
int ans=0;
int main()
{
	cin>>n>>m>>t;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		cin>>val[i][j];
		sum[i][j]+=(sum[i][j-1]+val[i][j]-'0');
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	for(int k=1;k<=m;k++)
	for(int q=j-1;q<k;q++)
	g[i][j][k]=max(g[i][j][k],g[i][j-1][q]+max(sum[i][k]-sum[i][q],k-q-(sum[i][k]-sum[i][q])));
	for(int i=1;i<=n;i++)
	for(int j=1;j<=t;j++)
	for(int k=0;k<=min(j,m);k++)
	dp[i][j]=max(dp[i][j],dp[i-1][j-k]+g[i][k][m]);
	for(int i=1;i<=n;i++)
	ans=max(ans,dp[i][t]);
	cout<<ans;
	return 0;
}
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值