20191023 练习:dp

文章目录

    • T1 P1060 开心的金明
    • T2 P1048 采药
    • T3 P1057 传球游戏
    • T4 P1095 守望者的逃离
    • T5 P1091 合唱队形
    • T6 P1077 摆花
    • T7 P5017 摆渡车
    • T8 P3957 跳房子
    • T9 道路游戏
    • T10 P1880 [NOI1995]石子合并
    • T11 P1063 能量项链
    • T12 P1970 花匠
    • T13 P1006 传纸条

T1 P1060 开心的金明

NOIP 2006 普及组 第二题
0/1背包
代码:

#include
using namespace std;
#define in Read()

int in{
	int s=0;char x;
	for(x=getchar();!isdigit(x);x=getchar());
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s;
}

int mon,n;
int v[30],p[30];
int dp[30][30005];

signed main(){
	mon=in,n=in;
	for(int i=1;i<=n;i++)
		v[i]=in,p[i]=in;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=mon;j++)
			dp[i][j]=(j>=v[i])?max(dp[i-1][j],dp[i-1][j-v[i]]+v[i]*p[i]):dp[i-1][j];
	printf("%d",dp[n][mon]);
	return 0;
}

T2 P1048 采药

NOIP2005普及组第三题
0/1背包
代码:

#include
using namespace std;
#define in Read()

int in{
	int s=0;char x;
	for(x=getchar();!isdigit(x);x=getchar());
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s;
}

const int A=1e4+5;
int t,m;
int use[A],v[A];
int dp[A][A];

signed main(){
	t=in,m=in;
	for(int i=1;i<=m;i++)
		use[i]=in,v[i]=in;
	for(int i=1;i<=m;i++)
		for(int j=1;j<=t;j++)
				dp[i][j]=(j>=use[i])?max(dp[i-1][j],dp[i-1][j-use[i]]+v[i]):dp[i-1][j];
	printf("%d",dp[m][t]);
	return 0;
}

T3 P1057 传球游戏

2008普及组第三题
简单dp
代码:

#include
using namespace std;
#define in Read()

int in{
	int s=0;char x;
	for(x=getchar();!isdigit(x);x=getchar());
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s;
}

int n,m;
int dp[35][35];

signed main(){
	n=in,m=in;
	dp[0][1]=1;
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++){
			if(j!=1&&j!=n)	dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
			if(j==1)	dp[i][1]=dp[i-1][2]+dp[i-1][n];
			if(j==n)	dp[i][n]=dp[i-1][n-1]+dp[i-1][1];
		}
	printf("%d",dp[m][1]);
	return 0;
}

T4 P1095 守望者的逃离

2007普及组
题目描述
恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变。守望者在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上。为了杀死守望者,尤迪安开始对这个荒岛施咒,这座岛很快就会沉下去。到那时,岛上的所有人都会遇难。守望者的跑步速度为 17 m / s 17m/s 17m/s,以这样的速度是无法逃离荒岛的。庆幸的是守望者拥有闪烁法术,可在 1 s 1s 1s内移动 60 m 60m 60m,不过每次使用闪烁法术都会消耗魔法值 10 10 10点。守望者的魔法值恢复的速度为 4 点 / s 4点/s 4/s,只有处在原地休息状态时才能恢复。
现在已知守望者的魔法初值 M M M,他所在的初始位置与岛的出口之间的距离 S S S,岛沉没的时间 T T T。你的任务是写一个程序帮助守望者计算如何在最短的时间内逃离荒岛,若不能逃出,则输出守望者在剩下的时间内能走的最远距离。注意:守望者跑步、闪烁或休息活动均以秒 ( s ) (s) (s)为单位,且每次活动的持续时间为整数秒。距离的单位为米 ( m ) (m) (m)

输入格式
共一行,包括空格隔开的三个非负整数 M , S , T M, S, T M,S,T

输出格式
共两行。
第1行为字符串 “ Y e s ” 或 “ N o ” “Yes”或“No” YesNo(区分大小写),即守望者是否能逃离荒岛。
第2行包含一个整数。第一行为 “ Y e s ” “Yes” Yes(区分大小写)时表示守望者逃离荒岛的最短时间;第一行为$“No”¥(区分大小写)时表示守望者能走的最远距离。

输入输出样例
输入
39 200 4
输出
No
197
输入
36 255 10
输出
Yes
6
说明/提示
30%的数据满足: 1 ≤ T ≤ 10 , 1 ≤ S ≤ 1001 ≤ T ≤ 10 , 1 ≤ S ≤ 100 1 \le T \le 10, 1 \le S \le 1001≤T≤10,1≤S≤100 1T10,1S1001T10,1S100
50%的数据满足: 1 ≤ T < ≤ 1000 , 1 ≤ S ≤ 100001 ≤ T < ≤ 1000 , 1 ≤ S ≤ 10000 1 \le T < \le 1000, 1 \le S \le 100001≤T<≤1000,1≤S≤10000 1T<1000,1S100001T<1000,1S10000
100%的数据满足: 1 ≤ T ≤ 300000 , 0 ≤ M ≤ 1000 , 1 ≤ S ≤ 1 0 8 1 ≤ T ≤ 300000 , 0 ≤ M ≤ 1000 , 1 ≤ S ≤ 1 0 8 1 \le T \le 300000,0 \le M \le 1000, 1 \le S \le 10^81≤T≤300000,0≤M≤1000,1≤S≤10^8 1T300000,0M1000,1S1081T300000,0M1000,1S108

思路:将跑步和法术分开处理,以时间为下标dp

代码:

#include
using namespace std;
#define in Read()

int in{
	int s=0,f=1;char x;
	for(x=getchar();!isdigit(x);x=getchar())	if(x=='-')	f=-1;
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s*f;
}

int m,s,t;
int dp[300005];

int main(){
	m=in,s=in,t=in;
	for(int i=1;i<=t;i++){
		if(m>=10){
			m-=10;
			dp[i]=dp[i-1]+60;
			continue;
		}
		dp[i]=dp[i-1];
		m+=4;
	}
	for(int i=1;i<=t;i++)
		dp[i]=max(dp[i],dp[i-1]+17);
	for(int i=1;i<=t;i++)
		if(dp[i]>=s){
			printf("Yes\n%d",i);
			return 0;
		}
	printf("No\n%d",dp[t]);
	return 0;
}

T5 P1091 合唱队形

2004提高组
思路:分别求出每点的从左向右和从右向左的最长上升序列,以此点为最高点的 a n s ans ans即为两者之和。
代码:

#include
using namespace std;
#define in Read()

int in{
	int s=0;char x;
	for(x=getchar();!isdigit(x);x=getchar());
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s;
}

const int A=105;
int n;
int h[A];
int l[A],r[A];
int res;

signed main(){
	n=in;
	for(int i=1;i<=n;i++)
		h[i]=in;
	for(int i=1;i<=n;i++){
		int maxx=0;l[i]=1;
		for(int j=1;j<i;j++)
			if(h[j]<h[i])	maxx=max(maxx,l[j]);
		l[i]+=maxx;
	}
	for(int i=n;i>=1;i--){
		int maxx=0;r[i]=1;
		for(int j=n;j>i;j--)
			if(h[j]<h[i])	maxx=max(maxx,r[j]);
		r[i]+=maxx;
	}
	for(int i=1;i<=n;i++)
		res=max(res,l[i]+r[i]-1);
	printf("%d",n-res);
	return 0;
}

T6 P1077 摆花

NOIP 2012 普及组 第三题
思路:dp摆到第i种花,第j盆花的方案数。
代码:

#include
using namespace std;
#define in Read()

int in{
	int s=0,f=1;char x;
	for(x=getchar();!isdigit(x);x=getchar())	if(x=='-')	f=-1;
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s*f;
}

const int A=1e3;
const int mod=1000007;
int n,m;
int a[A];
int dp[A][A],res;

int main(){
	n=in,m=in;
	for(int i=1;i<=n;i++)
		a[i]=in;
	fill(dp[1],dp[1]+a[1]+1,1);
	for(int i=2;i<=n;i++)
		for(int j=0;j<=m;j++)
			for(int k=max(j-a[i],0);k<=j;k++)
				dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
	printf("%d",dp[n][m]);
	return 0;
}

T7 P5017 摆渡车

2018普及组
题目描述
n n n名同学要乘坐摆渡车从人大附中前往人民大学,第 i i i位同学在第 t i t_i ti分钟去 等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发、把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费 m m m分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。
凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?
注意:摆渡车回到人大附中后可以即刻出发。

输入格式
第一行包含两个正整数 n , m n,m n,m,以一个空格分开,分别代表等车人数和摆渡车往返 一趟的时间。
第二行包含 n n n个正整数,相邻两数之间以一个空格分隔,第 i i i个非负整数 t i t_i ti代 表第 i i i个同学到达车站的时刻。

输出格式
输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。

输入输出样例
输入
5 1
3 4 4 3 5
输出
0
输入
5 5
11 13 1 5 5
输出
4
说明/提示
【输入输出样例 1 说明】
同学 1 1 1 和同学 4 4 4 在第 3 3 3 分钟开始等车,等待 0 0 0 分钟,在第 3 3 3 分钟乘坐摆渡车出发。摆渡车在第 4 4 4 分钟回到人大附中
同学 2 2 2 和同学 3 3 3 在第 4 4 4 分钟开始等车,等待 0 0 0 分钟,在第 4 4 4 分钟乘坐摆渡车 出发。摆渡车在第 5 5 5 分钟回到人大附中。
同学 5 5 5 在第 5 5 5 分钟开始等车,等待 0 0 0 分钟,在第 5 5 5 分钟乘坐摆渡车出发。自此 所有同学都被送到人民大学。总等待时间为 0 0 0
【输入输出样例 2 说明】
同学 3 3 3 在第 1 1 1 分钟开始等车,等待 0 0 0 分钟,在第 1 1 1 分钟乘坐摆渡车出发。摆渡 车在第 6 6 6 分钟回到人大附中。
同学 4 4 4 和同学 5 5 5 在第 5 5 5 分钟开始等车,等待 1 1 1 分钟,在第 6 6 6 分钟乘坐摆渡车 出发。摆渡车在第 11 11 11 分钟回到人大附中。
同学 11 11 11 在第 11 11 11 分钟开始等车,等待 2 2 2 分钟;同学 2 2 2 在第 13 13 13 分钟开始等车, 等待 0 0 0 分钟。他/她们在第 13 13 13 分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。 总等待时间为 4 4 4
可以证明,没有总等待时间小于 4 4 4 的方案。

【数据规模与约定】
n ≤ 500 , m ≤ 100 , 0 ≤ t i ≤ 4 × 1 0 6 . n\le 500,m\le 100,0\le t_i\le 4\times 10^6. n500,m100,0ti4×106.

思路
我们不妨认为时间是一条数轴,每名同学按照到达时刻分别对应数轴上可能重合的点。安排车辆的工作,等同于将数轴分成若干个区间,每段的长度即发车间隔 ⩾ m \geqslant m m。原本的等车时间之和,自然就转换成所有点到各自所属区间右边界的距离之和。
f i = m i n j ≤ i − m { f j + ∑ j < t k ≤ i ( i − t k ) f_i=min_{j\le i-m}\lbrace f_j+\sum_{j\lt t_k\le i}(i-t_k) fi=minjim{fj+j<tki(itk)
以时间为下标,求点数的前缀和( n u m num num),求点数值的前缀和( s u m sum sum)。
有: ∑ j < t k ≤ i ( i − t k ) = i × ( n u m i − n u m j − 1 ) − ( s u m i − s u m j − 1 ) \sum_{j\lt t_k\le i}(i-t_k)=i\times (num_i-num_{j-1})-(sum_i-sum_{j-1}) j<tki(itk)=i×(numinumj1)(sumisumj1)
最后在 [ t , t + m ) [t,t+m) [t,t+m)中取最小值即为答案。
剪枝:当 [ i , j ] [i,j] [i,j]中没有点,即 n u m j − n u m i − 1 = = 0 num_j-num_{i-1}==0 numjnumi1==0时,直接转移 f j = f i f_j=f_i fj=fi即可。

代码:

#include
using namespace std;
#define in Read()
#define int long long

int in{
	int s=0;char x;
	for(x=getchar();!isdigit(x);x=getchar());
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s;
}

const int A=4e6+5;
int n,m;
int t[A];
int sum[A],cnt[A];
int f[A],res=1e9;

signed main(){
	n=in,m=in;
	for(int i=1;i<=n;i++)	t[i]=in;
	sort(t+1,t+1+n);
	for(int i=1;i<=n;i++){
		sum[t[i]]+=t[i];
		cnt[t[i]]++;
	}
	for(int i=t[1];i<=t[n]+m;i++){
		sum[i]+=sum[i-1],cnt[i]+=cnt[i-1];
	}
	fill(f+t[1]+1,f+1+t[n]+m,1e9);
	for(int i=t[1];i<=t[n]+m;i++)
		for(int j=max((int)0,i-m);j<=i;j++){
			if(cnt[i]-cnt[i-m]==0){f[i]=f[i-m];continue;}
			f[i]=min(f[i],(f[j-m]-(sum[i-1]-sum[j-m])+i*(cnt[i-1]-cnt[j-m])));
		}
	for(int i=t[n];i<=t[n]+m;i++)
		res=min(res,f[i]);
	printf("%lld",res);
	return 0;
}

T8 P3957 跳房子

2017普及组
题目描述
跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。
跳房子的游戏规则如下:
在地面上确定一个起点,然后在起点右侧画 n n n 个格子,这些格子都在同一条直线上。每个格子内有一个数字(整数),表示到达这个 格子能得到的分数。玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定:
玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。
现在小 R R R 研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的 d d d 。小 R R R 希望改进他的机器人,如果他花 g g g 个金币改进他的机器人,那么他的机器人灵活性就能增加 g g g ,但是需要注意的是,每 次弹跳的距离至少为 1 1 1 。具体而言,当 g < d gg<d 时,他的机器人每次可以选择向右弹跳的距离为 d − g , d − g + 1 , d − g + 2 d − g , d − g + 1 , d − g + 2 , . . . . . . , d + g − 2 , d + g − 1 , d + g d-g,d-g+1,d-g+2d−g,d−g+1,d−g+2,......,d+g-2,d+g-1,d+g dg,dg+1,dg+2dg,dg+1,dg+2,......,d+g2,d+g1,d+g ;否则( 当 g ≥ d 时 当 g \geq d 时 gd),他的机器人每次可以选择向右弹跳的距离为 1 , 2 , 3 , . . . . . . , d + g − 2 , d + g − 1 , d + g 1,2,3,......,d+g-2,d+g-1,d+g 1,2,3,......,d+g2,d+g1,d+g
现在小 R R R 希望获得至少 k k k 分,请问他至少要花多少金币来改造他的机器人。

输入格式
第一行三个正整数 n , d , k n,d,k ndk ,分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数。相邻两个数 之间用一个空格隔开。
接下来 n n n 行,每行两个正整数 x i , s i x_i,s_i xi,si,分别表示起点到第 i i i 个格子的距离以及第 i i i 个格子的分数。两个数之间用一个空格隔开。保证 x i x_i xi按递增顺序输入。

输出格式
共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获得至少 k k k 分,输出 − 1 −1 1

输入输出样例
输入
7 4 10
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
输出
2
输入
7 4 20
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
输出
-1
说明/提示
【输入输出样例 1 说明】 2 2 2个金币改进后, 小 $R¥ 的机器人依次选择的向右弹跳的距离分别为 2 , 3 , 5 , 3 , 4 , 3 2, 3, 5, 3, 4,3 2,3,5,3,4,3, 先后到达的位置分别为 2 , 5 , 10 , 13 , 17 , 20 2, 5, 10, 13, 17, 20 2,5,10,13,17,20, 对应 1 , 2 , 3 , 5 , 6 , 7 1, 2, 3, 5, 6, 7 1,2,3,5,6,7 6 6 6个格子。这些格子中的数字之和 15 15 15即为小 R R R 获得的分数。
输入输出样例 2 说明
由于样例中 7 7 7 个格子组合的最大可能数字之和只有 18 18 18 ,无论如何都无法获得 20 20 20

数据规模与约定
本题共 10 组测试数据,每组数据 10 分。

对于全部的数据满足 1 ≤ n ≤ 500000 , 1 ≤ d ≤ 2000 , 1 ≤ x i , k ≤ 1 0 9 , ∣ s i ∣ < 1 0 5 1 ≤ n ≤ 500000, 1 ≤ d ≤2000, 1 ≤ x_i, k ≤ 10^9,|s_i|\lt 10^5 1n500000,1d2000,1xi,k109,si<105

思路:二分答案+dp验证(跳到第 i i i个点的最大分数)+单调队列优化(能跳到第 i i i个点的点中最大的dp值)

代码:

#include
using namespace std;
#define in Read()
#define int long long

int in{
	int s=0,f=1;char x;
	for(x=getchar();!isdigit(x);x=getchar())	if(x=='-')	f=-1;
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s*f;
}

const int A=5e5+5;
int n,d,p;
struct gg{int f,s;}a[A];
int all;
int dp[A];

bool pd(int mon){
	int lcan=max(d-mon,(int)1),rcan=d+mon;
	deque <int> q;
	memset(dp,0,sizeof(dp));
	int add=1;
	for(int i=1;i<=n;i++){
		while(!q.empty()&&a[i].s-a[q.front()].s>rcan)
			q.pop_front();
		while(a[i].s-a[add].s>=lcan){
			if(dp[add]==-1e9){
				add++;
				continue;
			}	
			if(a[i].s-a[add].s>rcan){
				add++;
				continue;
			}
			while(!q.empty()&&dp[add]>=dp[q.back()])
				q.pop_back();
			q.push_back(add);
			add++;
		}
		dp[i]=(!q.empty())?dp[q.front()]+a[i].f:-1e9;
		if(a[i].s>=lcan&&a[i].s<=rcan)	dp[i]=max(dp[i],a[i].f);
	}
	for(int i=1;i<=n;i++)
		if(dp[i]>=p)	return 1;
	return 0;
}

signed main(){
	n=in,d=in,p=in;
	for(int i=1;i<=n;i++){
		a[i].s=in,a[i].f=in;
		if(a[i].f>0)	all+=a[i].f;
	}
	if(all<p){
		printf("-1");
		return 0;
	}
	int l=0,r=max(a[n].s-d,d);
	while(l<r){
		int mid=(l+r)>>1;
		if(pd(mid))	r=mid;
		else	l=mid+1;
	}
	printf("%lld",l);
	return 0;
}

T9 道路游戏

NOIP 2009 普及组 第四题
题目描述
小新正在玩一个简单的电脑游戏。

游戏中有一条环形马路,马路上有 n n个机器人工厂,两个相邻机器人工厂之间由一小段马路连接。小新以某个机器人工厂为起点,按顺时针顺序依次将这 n n个机器人工厂编号为1-n1−n,因为马路是环形的,所以第 nn 个机器人工厂和第 1 1个机器人工厂是由一段马路连接在一起的。小新将连接机器人工厂的这 n 段马路也编号为 1-n1−n,并规定第 i i段马路连接第 i 个机器人工厂和第 i+1i+1 个机器人工厂(1≤i≤n-11≤i≤n−1),第 nn 段马路连接第 nn 个机器人工厂和第 11个机器人工厂。

游戏过程中,每个单位时间内,每段马路上都会出现一些金币,金币的数量会随着时间发生变化,即不同单位时间内同一段马路上出现的金币数量可能是不同的。小新需要机器人的帮助才能收集到马路上的金币。所需的机器人必须在机器人工厂用一些金币来购买,机器人一旦被购买,便会沿着环形马路按顺时针方向一直行走,在每个单位时间内行走一次,即从当前所在的机器人工厂到达相邻的下一个机器人工厂,并将经过的马路上的所有金币收集给小新,例如,小新在 ii(1≤i≤n1≤i≤n)号机器人工厂购买了一个机器人,这个机器人会从 ii 号机器人工厂开始,顺时针在马路上行走,第一次行走会经过 i i号马路,到达 i+1 i+1号机器人工厂(如果 i=ni=n,机器人会到达第 11 个机器人工厂),并将 ii 号马路上的所有金币收集给小新。 游戏中,环形马路上不能同时存在 2 2个或者 2 2个以上的机器人,并且每个机器人最多能够在环形马路上行走p p次。小新购买机器人的同时,需要给这个机器人设定行走次数,行走次数可以为 1~p1 p 之间的任意整数。当马路上的机器人行走完规定的次数之后会自动消失,小新必须立刻在任意一个机器人工厂中购买一个新的机器人,并给新的机器人设定新的行走次数。

以下是游戏的一些补充说明:

游戏从小新第一次购买机器人开始计时。

购买机器人和设定机器人的行走次数是瞬间完成的,不需要花费时间。

购买机器人和机器人行走是两个独立的过程,机器人行走时不能购买机器人,购买完机器人并且设定机器人行走次数之后机器人才能行走。

在同一个机器人工厂购买机器人的花费是相同的,但是在不同机器人工厂购买机器人的花费不一定相同。

购买机器人花费的金币,在游戏结束时再从小新收集的金币中扣除,所以在游戏过程中小新不用担心因金币不足,无法购买机器人而导致游戏无法进行。也因为如此,游戏结束后,收集的金币数量可能为负。

现在已知每段马路上每个单位时间内出现的金币数量和在每个机器人工厂购买机器人需要的花费,请你告诉小新,经过 mm 个单位时间后,扣除购买机器人的花费,小新最多能收集到多少金币。

输入格式
第一行 33 个正整数n,m,pn,m,p,意义如题目所述。

接下来的 n n行,每行有 mm 个正整数,每两个整数之间用一个空格隔开,其中第 i 行描

述了 ii 号马路上每个单位时间内出现的金币数量(1≤1≤金币数量≤100≤100),即第 i i行的第 jj(1≤j≤m1≤j≤m)个数表示第 jj 个单位时间内 i 号马路上出现的金币数量。

最后一行,有 nn 个整数,每两个整数之间用一个空格隔开,其中第 ii 个数表示在 i i号机器人工厂购买机器人需要花费的金币数量(1≤1≤金币数量≤100≤100)。

输出格式
共一行,包含 1 1个整数,表示在 mm 个单位时间内,扣除购买机器人

花费的金币之后,小新最多能收集到多少金币。

输入输出样例
输入
2 3 2
1 2 3
2 3 4
1 2
输出
5
说明/提示
【数据范围】

对于 40%的数据,2≤n≤40,1≤m≤402≤n≤40,1≤m≤40。

对于 90%的数据,2≤n≤200,1≤m≤2002≤n≤200,1≤m≤200。

对于 100%的数据,2≤n≤1000,1≤m≤1000,1≤p≤m2≤n≤1000,1≤m≤1000,1≤p≤m。

NOIP 2009 普及组 第四题

思路:先求前缀和(二维),方便求区间和。再三重循环(机器人死掉的时间,地点,死前的步数)。
注:用mod搞掉回环。

状态转移方程:
f [ i ] = m a x ( f [ i − k ] + s u m [ i ] [ j ] − s u m [ i − k ] [ j − k ] − c o s t [ j − k ] ) f[i]=max(f[i−k]+sum[i][j]−sum[i-k][j-k]−cost[j−k]) f[i]=max(f[ik]+sum[i][j]sum[ik][jk]cost[jk])

好题解:
(https://www.luogu.org/blog/Fenian/p1070-dao-lu-you-hu-ti-xie)
P1070道路游戏题解
一道非常具有思考性的dp。
用f[i]表示第i个时间最大利益
cost[i]表示第i个工厂机器人的雇佣费
r[j][i]表示第i个时刻到达第j个工厂的价值
sum[i][j]表示价值的前缀和
首先,我们将第一个工厂编号为0( It is important 一定要理解这一操作)。
因为第i条路指向第i+1个工厂,所以我们可以将第i条路的价值变成第i+1个工厂的价值,于是我们得到如下输入代码(因为从0开始编号,所以第n条路的价值转移给第0号工厂,因此对n取模)

for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&r[i%n][j];

同时,我们对价值进行前缀和。因为价值存在工厂和时间两个约束,所以我们使用二维前缀和。

for(int i=1;i<=m;i++)
        for(int j=0;j<n;j++)
            sum[i][j]=sum[i-1][(j-1+n)%n]+r[j][i];

即sum值由对角线传递
*我们从O(n3)的解法讲起
我们很容易得到这个转移方程
f [ i ] = m a x ( f [ i − k ] + s u m [ i ] [ j ] − s u m [ i − k ] [ j − k ] − c o s t [ j − k ] ) f[i]=max(f[i−k]+sum[i][j]−sum[i-k][j-k]−cost[j−k]) f[i]=max(f[ik]+sum[i][j]sum[ik][jk]cost[jk])
意思是机器人从j-k号工厂出发,走k次在i时刻到达j号工厂,并对这一过程取最优值。
如果是以这道题考察普及组的初衷,那么到这里就结束,但是如果数据稍稍加强,对于1000的数据,O(n3)是远不够得。
*那么,我想办法进行压缩
我们对上面的方程稍加处理得到
f [ i ] = m a x ( f [ i − k ] − s u m [ i − k ] [ j − k ] − c o s t [ j − k ] ) + s u m [ i ] [ j ] f[i]=max(f[i−k]−sum[i-k][j-k]−cost[j−k])+sum[i][j] f[i]=max(f[ik]sum[ik][jk]cost[jk])+sum[i][j]
就是将不需要枚举k步数的项提出来。
我们用 r e c [ i ] [ j rec[i][j rec[i][j]来记录 f [ i ] − s u m [ i ] [ j ] − c o s t [ j ] f[i]−sum[i][j]−cost[j] f[i]sum[i][j]cost[j]
得到
f [ i ] = m a x ( r e c [ i − k ] [ j − k ] ) + s u m [ i ] [ j ] f[i]=max(rec[i-k][j-k])+sum[i][j] f[i]=max(rec[ik][jk])+sum[i][j]
因为rec的i和j两项同时减去k,所以实际上f[i]是对对角线上的rec进行取最大值。我们用优先队列对这一过程进行优化,将同一对角线的rec放入同一个优先队列。
详细的细节,还是看看代码吧(第二个)

程序(三重循环 O( n 3 n^3 n3)):

#include
using namespace std;

const int A=1005;

int n,m,p;
int mon[A][A],pay[A];
int sum[A][A];
int r[A];

int main()
{
	scanf("%d%d%d",&n,&m,&p);
	fill(r+1,r+1+m,-1e8);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&mon[j][(i+1)%n]);
	for(int i=1;i<=n;i++)
		scanf("%d",&pay[i%n]);
	for(int i=1;i<=m;i++)
		for(int j=0;j<n;j++)
			sum[i][j]=sum[i-1][(j-1+n)%n]+mon[i][j];
	for(int i=1;i<=m;i++)
		for(int j=0;j<n;j++)
			for(int k=1;k<=min(i,p);k++)
				r[i]=max(r[i],r[i-k]+sum[i][j]-sum[i-k][(j-k+n)%n]-pay[(j-k+n)%n]);
	printf("%d",r[m]);
	return 0;
}

单调队列没 写出来。。。(:逃
标程(优先队列):
(https://www.luogu.org/problemnew/solution/P1070)

#include
#include
#include
using namespace std;
inline int read()
{
    int x=0;
    char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    return x;
}

int n,m,p;
int r[1005][1005];
int cost[1005];
int sum[1005][1005];
int rec[1005][1005];
int dp[1005];
int pre[1005][1005];
int maxn=-99999999;

struct node{
    int data,step;
    bool operator<(node a)const{
        return a.data>data;
    }
};

priority_queue<node>q[1100];

void update(int si,int now)
{
    node tmp=q[si].top();
    while(now-tmp.step>=p)
    {
        q[si].pop();
        tmp=q[si].top();
    }
}

int main()
{
    n=read();m=read();p=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            r[i%n][j]=read();
    for(int i=1;i<=m;i++)
        for(int j=0;j<n;j++)
            sum[i][j]=sum[i-1][(j-1+n)%n]+r[j][i];
    for(int i=0;i<n;i++)
        cost[i]=read();
    for(int i=0;i<n;i++)
        pre[0][(i-1+n)%n]=i;
    for(int i=0;i<n;i++)
    {
        rec[0][i]=-cost[i];
        node tmp;
        tmp.data=-cost[i];
        tmp.step=0;
        q[pre[0][i]].push(tmp);
    }
    for(int i=1;i<=m;i++)
    {
        int t=-9999999;
        for(int j=0;j<n;j++)
        {
            dp[i]=max(dp[i],rec[i-1][(j-1+n)%n]+sum[i][j]);
            maxn=max(maxn,dp[i]);
            t=max(t,dp[i]);
        }
        for(int j=0;j<n;j++)
        {
            pre[i][j]=pre[i-1][(j-1+n)%n];
            node tmp;
            tmp.data=t-sum[i][j]-cost[j];
            tmp.step=i;
            q[pre[i][j]].push(tmp);
            update(pre[i][j],i);
            rec[i][j]=q[pre[i][j]].top().data;
        }
    }
    cout<<maxn;
    return 0;
}

T10 P1880 [NOI1995]石子合并

NOI1995

思路
若最初的第 L L L堆石子和第 R R R堆石子被合并成一堆,则说明 L ~ R L~R LR之间的每堆石子也已经被合并,这样 L L L R R R才能相邻。因此,任意时刻,任意一堆石子均可以用一个闭区间 [ L , R ] [L,R] [L,R]来描述,表示这堆石子由最初的 L ~ R L~R LR堆石子合并而成,起点重量为: ∑ a i ( L ≤ i ≤ R ) ∑ai(L≤i≤R) ai(LiR)。 另外一定存在一个整数 k ( L ≤ k < R ) k(L≤k<R) k(LkR),在这堆石子形成之前,先有第 L ~ k L~k Lk堆石子被合并成一堆,第 ( k + 1 ) ~ R (k+1)~R (k+1)R堆石子被合并成一堆,然后这两堆石子才合并成 [ L , R ] [L,R] [L,R]
a [ i ] a[i] a[i]表示第 i i i堆石子数量。
s [ i ] s[i] s[i]表示从第 1 1 1堆到第i堆石子数总和: s [ i ] = a [ 1 ] + a [ 2 ] + . . . + a [ i ] s[i]=a[1]+a[2]+...+a[i] s[i]=a[1]+a[2]+...+a[i],可以用前缀和求出。
F m a x [ i ] [ j ] Fmax[i][j] Fmax[i][j]表示从第i堆石子合并到第j堆石子的最大的得分。
F m i n [ i ] [ j ] Fmin[i][j] Fmin[i][j]表示从第i堆石子合并到第j堆石子的最小的得分。
则状态转移方程为:
F m a x [ i ] [ j ] = m a x { F m a x [ i ] [ k ] + F m a x [ k + 1 ] [ j ] + s [ j ] − s [ i − 1 ] } . ( i ≤ k ≤ j − 1 ) Fmax[i][j]=max\lbrace Fmax[i][k]+Fmax[k+1][j]+s[j]-s[i-1]\rbrace.(i≤k≤j-1) Fmax[i][j]=max{Fmax[i][k]+Fmax[k+1][j]+s[j]s[i1]}.(ikj1)
F m i n [ i ] [ j ] = m i n { F m i n [ i ] [ k ] + F m i n [ k + 1 ] [ j ] + s [ j ] − s [ i − 1 ] } ( i ≤ k ≤ j − 1 ) Fmin[i][j]=min\lbrace Fmin[i][k]+Fmin[k+1][j]+s[j]-s[i-1]\rbrace(i≤k≤j-1) Fmin[i][j]=min{Fmin[i][k]+Fmin[k+1][j]+s[j]s[i1]}(ikj1)
初始条件: F m a x [ i ] [ i ] = 0 , F m i n [ i ] [ i ] = 0 Fmax[i][i]=0,Fmin[i][i]=0 Fmax[i][i]=0Fmin[i][i]=0
时间复杂度为 O ( n 3 ) O(n^3) O(n3)

即dp一段石子合并成一堆石子时的最大和最小分数

代码:

#include
using namespace std;
#define in Read()

int in{
	int s=0,f=1;char x;
	for(x=getchar();!isdigit(x);x=getchar())	if(x=='-')	f=-1;
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s*f;
}

const int A=500;
int n;
int a[A];
int minn[A][A],maxx[A][A];
int sum[2*A];
int r1=1e9,r2=0;

int main(){
	n=in;
	for(int i=1;i<=n;i++)
		a[i+n]=a[i]=in;
	for(int i=1;i<=2*n;i++)
		sum[i]=sum[i-1]+a[i];
	for(int l=2;l<=n;l++)
		for(int i=1;i<=2*n-l+1;i++){
			int j=i+l-1;
			minn[i][j]=1e9,maxx[i][j]=0;
			for(int k=i;k<j;k++){
				minn[i][j]=min(minn[i][j],minn[i][k]+minn[k+1][j]);
				maxx[i][j]=max(maxx[i][j],maxx[i][k]+maxx[k+1][j]);
			}
			minn[i][j]+=sum[j]-sum[i-1];
			maxx[i][j]+=sum[j]-sum[i-1];
		}
	for(int i=1;i<=n-1;i++){
		r1=min(r1,minn[i][i+n-1]);
		r2=max(r2,maxx[i][i+n-1]);
	}
	printf("%d\n%d",r1,r2);
	return 0;
}

T11 P1063 能量项链

NOIP 2006 提高组 第一题

思路:同T10 合并石子

代码:

#include
using namespace std;
#define in Read()
#define int long long

int in{
	int s=0,f=1;char x;
	for(x=getchar();!isdigit(x);x=getchar())	if(x=='-')	f=-1;
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s*f;
}

const int A=1e3+5;
int n;
struct gg{int head,tail;}a[A];
int dp[A][A];
int ans;

signed main(){
	n=in;
	for(int i=1;i<=n;i++)
		a[i+n].head=a[i+n-1].tail=a[i].head=a[i-1].tail=in;
	a[n].tail=a[1].head;
	for(int l=2;l<=n;l++)
		for(int i=1;i<=2*n-l+1;i++){
			int j=i+l-1;
			for(int k=i;k<j;k++)
				dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i].head*a[k].tail*a[j].tail);
		}
	for(int i=1;i<=n;i++)
		ans=max(ans,dp[i][i+n-1]);
	printf("%d",ans);
	return 0;
}

T12 P1970 花匠

题目描述
花匠栋栋种了一排花,每株花都有自己的高度。花儿越长越大,也越来越挤。栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希望剩下的花排列得比较别致。

具体而言,栋栋的花的高度可以看成一列整数 h 1 , h 2 . . . h n h_1,h_2...h_n h1,h2...hn。设当一部分花被移走后,剩下的花的高度依次为 g 1 , g 2 . . . g m g_1,g_2...g_m g1,g2...gm,则栋栋希望下面两个条件中至少有一个满足:
条件 A A A:对于所有 g 2 i > g 2 i + 1 , g 2 i > g 2 i − 1 g_{2i}\gt g_{2i+1},g_{2i}\gt g_{2i-1} g2i>g2i+1,g2i>g2i1
条件 B B B:对于所有 g 2 i < g 2 i + 1 , g 2 i < g 2 i − 1 g_{2i}\lt g_{2i+1},g_{2i}\lt g_{2i-1} g2i<g2i+1,g2i<g2i1
注意上面两个条件在 m = 1 m=1 m=1时同时满足,当 m > 1 m > 1 m>1时最多有一个能满足。
请问,栋栋最多能将多少株花留在原地。

输入格式
第一行包含一个整数 n n n,表示开始时花的株数。
第二行包含 n n n个整数,依次为 h 1 , h 2 . . . h n h_1,h_2...h_n h1,h2...hn,表示每株花的高度。

输出格式
一个整数 m m m,表示最多能留在原地的花的株数。

输入输出样例
输入
5
5 3 2 1 2
输出
3
说明/提示
【输入输出样例说明】

有多种方法可以正好保留 3 3 3 株花,例如,留下第 1 、 4 、 5 1、4、5 145 株,高度分别为 5 、 1 、 2 5、1、2 512,满足条件 B。

【数据范围】
1 ≤ n ≤ 1 0 5 , 0 ≤ h i ≤ 1 0 6 1\leq n\leq 10^5,0\leq h_i\leq 10^6 1n105,0hi106

思路:即求最大波浪,贪心即可(当前趋势上升时,遇到下降的点就选,反之亦然)

代码:

#include
using namespace std;
#define in Read()

int in{
	int s=0,f=1;char x;
	for(x=getchar();!isdigit(x);x=getchar())	if(x=='-')	f=-1;
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s*f;
}

const int A=1e5+5;
int n;
int h[A];
int ans=2;
bool bz=0;

signed main(){
	n=in;
	if(n==1){
		printf("1");
		return 0;
	}
	for(int i=1;i<=n;i++)
		h[i]=in;
	for(int i=1;i<=n;i++){
		if(h[i]==h[i+1])	continue;
		if(h[i]>h[i+1]){bz=0;break;}
		if(h[i]<h[i+1]){bz=1;break;}
	}
	for(int i=2;i<=n;i++){
		if(bz==0)	if(h[i]>h[i-1])	{bz=1;ans++;continue;}
		if(bz==1)	if(h[i]<h[i-1])	{bz=0;ans++;continue;}
	}
	printf("%d",ans);
	return 0;
}

T13 P1006 传纸条

NOIP 2008提高组第三题
题目描述
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个 m m m n n n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 ( 1 , 1 ) (1,1) (1,1),小轩坐在矩阵的右下角,坐标 ( m , n ) (m,n) (m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 0 0表示),可以用一个 0 − 100 0-100 0100的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这 2 2 2条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的 2 2 2条路径。

输入格式
输入文件,第一行有 2 2 2个用空格隔开的整数 m m m n n n,表示班里有 m m m n n n列。
接下来的 m m m行是一个 m × n m \times n m×n的矩阵,矩阵中第 i i i j j j列的整数表示坐在第 i i i j j j列的学生的好心程度。每行的 n n n个整数之间用空格隔开。

输出格式
输出文件共一行,包含一个整数,表示来回 2 2 2条路上参与传递纸条的学生的好心程度之和的最大值。

输入输出样例
输入
3 3
0 3 9
2 8 5
5 7 0
输出
34

说明/提示
【限制】
30%的数据满足: 1 ≤ m , n ≤ 10 , 1 ≤ m , n ≤ 10 1 \le m,n \le 10,1≤m,n≤10 1m,n10,1m,n10
100%的数据满足: 1 ≤ m , n ≤ 50 , 1 ≤ m , n ≤ 50 1 \le m,n \le 50,1≤m,n≤50 1m,n50,1m,n50

思路
题意可以转化为小渊同时向小轩同时传两张不同路线的纸条,使得纸条路线上的数字和最大。
f [ a ] [ b ] [ x ] [ y ] f[a][b][x][y] f[a][b][x][y]表示第一张纸条传到 ( a , b ) (a,b) (a,b),第二张纸条传到 ( x , y ) (x,y) (x,y)时,路线上的最大数字和。
使 y < b y\lt b y<b以保证路线没有重复。
因为只能向右、下传,所以一个格子的状态从其左、上转移来。
因为只有 ( m − 1 , n ) 和 ( m , n − 1 ) (m-1,n)和(m,n-1) (m1,n)(m,n1)能到 ( m , n ) (m,n) (m,n)所以答案为 f [ m ] [ n − 1 ] [ m − 1 ] [ n ] f[m][n-1][m-1][n] f[m][n1][m1][n]
状态转移方程: f [ a ] [ b ] [ x ] [ y ] = m a x ( f [ a − 1 ] [ b ] [ x − 1 ] [ y ] , f [ a − 1 ] [ b ] [ x ] [ y − 1 ] , f [ a ] [ b − 1 ] [ x − 1 ] [ y ] , f [ a ] [ b − 1 ] [ x ] [ y − 1 ] ) f[a][b][x][y]=max(f[a-1][b][x-1][y],f[a-1][b][x][y-1],f[a][b-1][x-1][y],f[a][b-1][x][y-1]) f[a][b][x][y]=max(f[a1][b][x1][y],f[a1][b][x][y1],f[a][b1][x1][y],f[a][b1][x][y1])

代码:

#include
using namespace std;
#define in Read()
int in{
	int s=0;char x;
	for(x=getchar();!isdigit(x);x=getchar());
	for( ;isdigit(x);x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return s;
}

int n,m;
int p[55][55];
int f[55][55][55][55];

signed main(){
	n=in,m=in;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			p[i][j]=in;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int u=1;u<=n;u++)
				for(int v=j+1;v<=m;v++)
					f[i][j][u][v]=max(max(f[i-1][j][u-1][v],f[i-1][j][u][v-1]),max(f[i][j-1][u-1][v],f[i][j-1][u][v-1]))+p[i][j]+p[u][v];
	printf("%d",f[n][m-1][n-1][m]);
				
			
	return 0;
}

你可能感兴趣的:(练习)