【杂题总汇】Codeforces-67A Partial Teacher

【Codeforces-67A】Partial Teacher

上周刷了一大堆小紫薯的动态规划的题😏……老师给我们布置了一场测试,感觉还好吧……虽然我并不是这么擅长动态规划😜

+Codeforces 传送门+


 

◇ 题目

<手写翻译>

老师要给n个学生发糖,n个学生排成一队。由于老师有偏见🤨,对于一对相邻的学生,他会给成绩好的学生多一些糖(严格大于),而成绩好的学生少一些糖(严格小于),若相邻学生成绩相等,那么他们得到的糖的数量也是一样的。每个学生都至少得到一颗糖。求老师最少总共要给学生多少糖,输出给最少的糖的方案。

<输入输出>

单组数据。第一行输入n,表示学生的数量,学生编号为1~n,且1~n从左到右排列。第二行一个字符串描述了相邻学生的成绩关系,第i个字符描述的是第i个学生和和第i+1个学生的成绩关系,L表示第i个学生的成绩更好,R表示第i+1个学生的成绩更好,=表示两人成绩相等。

<样例&没解释🙃>

第一组数据 第二组数据
Input Output Input Output

5
LRLR

2 1 2 1 2

5
=RRR

1 1 2 3 4

 

 

 

 

 


 ◇ 解析

这道题其实是一道比较普通的DP,但是也有一点特别,至于是什么之后再讲。

让我们先忽略数据规模。

碰到这种题,我的第一想法就是暴力DP,所以就定义了一个非常暴力的状态:dp[i][j]表示第i个学生得到j颗糖,前i个学生得到的最少糖数。然后又加上了一个非常暴力的状态转移,用填表法,按照第i个和第i-1个学生成绩的关系分为3类——

①若第i个学生成绩好,则第i个学生分到的糖大于第i-1个学生的糖,即应从dp[i-1][小于j]转移过来,则有 dp[i][j]=min{dp[i-1][1~j-1]}+j;

②若第i-1个学生成绩好,则第i个学生分到的糖小于第i-1个学生的糖,即应从dp[i-1][大于j]转移过来,则有 dp[i][j]=min{dp[i-1][j+1~n]}+j;

③若两人成绩相等,则直接得到 dp[i][j]=dp[i-1][j]+j;

初始化时将dp[1][i]初始化为i,因为第一个学生没法与前一个学生对比。

于是我就得到了下面的伪代码:

for(枚举i,2~n)
  for(枚举j,1~n)
  {
    if(第i个学生成绩好)
      for(枚举k,1~j-1) 转移;
    if(第i-1个学生成绩好)
      for(枚举k,j+1~n) 转移;
    if(两人成绩相等)
      转移;
  }

可见这是一个 O(n3)的算法,再看一眼数据规模……n≤1000。这样肯定会TLE的,于是我就没敢提交😱。

是状态定义错误吗?好像其他状态定义没法转移……于是我开始想一些优化!

成绩相等的情况下是不需要优化的,本来就是O(n2)的,唯一需要优化的就是剩下的两种情况。注意到我们的转移,一种情况是从大于j的状态转移,另一种是从小于j的状态转移。于是我想到定义一个Min,初值为正无穷INF,当从大于j的状态转移时,表示的是 dp[i-1][j+1~n] 的最小值;当从小于j的状态转移时,表示的是 dp[i-1][1~j-1] 的最小值。

由于我们求 dp[i][j] 时,j的枚举顺序是对答案没有影响的,我进行了如下操作:

①若第i个学生成绩好,将j从小到大枚举,并在求dp[i][j]之前更新Min为 Min=min(Min,dp[i-1][j-1]),这样下来,Min的值就是 dp[i-1][1~j-1] 的最小值了,于是我们就可以改变转移式为 dp[i][j]=Min+j,瞬间少了O(n)(一层循环)的复杂度😃;

②若第i-1个学生成绩好,将j从大到小枚举,并在求dp[i][j]之前更新Min为min(Min,dp[i-1][j+1]),按照之前的思路,转移式简化为 dp[i][j]=Min+j。

于是就可以非常愉快地进行DP了!!

但是题目要求的是输出方案,这是递推式dp的难点。我们存储一个fa[i][j]对应dp[i][j],表示的是 dp[i][j] 的状态值是由 dp[i-1][fa[i][j]] 转移过来的(由于第一维状态始终是由i-1转移而来,第一维不需要存储)。

那么我们如何找到 fa[i][j] ?由于 dp[i][j] 始终是从最小值,也就是 Min 转移过来,所以我们再定义一个变量Fa,表示当前状态是由 dp[i-1][Fa] 转移来的,在更新Min时,若Min被另一个值更新,则更新Fa为其第二维,转移时同时将 fa[i][j] 的值改为 Fa。

我们的最终答案是 dp[n][1~n] 转移过来的,我们定义pos为 dp[n][1~n] 中的最小值的第二维。所以我们可以递归输出答案,从 (n,pos) 开始,利用fa[i][j]倒推回 i=1 。

具体见代码~


 

◇ 源代码

/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1000;
int n,INF;
char gra[MAXN+5]; //学生成绩关系
int dp[MAXN+5][MAXN+5],fa[MAXN+5][MAXN+5]; //dp和fa一一对应
int ans,pos;
void Print(int x,int y){ //递归输出
	if(x<0) return; //全部结束
	Print(x-1,fa[x][y]);
	if(x>0) printf(" "); //处理空格
	printf("%d",y);
}
int main(){
	scanf("%d%s",&n,gra+1); //gra从1开始
	memset(dp,0x3f,sizeof dp); //初始化为最大值
	INF=dp[0][0];
	ans=INF;
	for(int i=1;i<=n;i++) dp[0][i]=i; //这里我把学生的编号改为0开始,也就是0~n-1
	for(int i=1;i<n;i++)
	{
		int Min,Fa;
		switch(gra[i])
		{
			case '=': //直接转移
				for(int j=1;j<=n;j++)
				{
					dp[i][j]=dp[i-1][j];
					fa[i][j]=j;
					if(i==n-1) //最后的状态,找寻答案
						if(ans>dp[i][j])
							ans=dp[i][j],
							pos=j; //记录第二维
				}
				break;
			case 'R':
				Min=INF;
				for(int j=1;j<=n;j++) //顺序枚举
				{
					if(Min>dp[i-1][j-1]) //更新Min
					{
						Fa=j-1; //同时更新Fa
						Min=dp[i-1][j-1];
					}
					if(dp[i][j]>Min+j) //转移
						dp[i][j]=Min+j,
						fa[i][j]=Fa;
					if(i==n-1)
						if(ans>dp[i][j])
							ans=dp[i][j],
							pos=j;
				}
				break;
			case 'L':
				Min=INF;
				for(int j=n;j>=1;j--) //逆序枚举
				{
					if(Min>dp[i-1][j+1])
					{
						Fa=j+1;
						Min=dp[i-1][j+1];
					}
					if(dp[i][j]>Min+j)
						dp[i][j]=Min+j,
						fa[i][j]=Fa;
					if(i==n-1)
						if(ans>dp[i][j])
							ans=dp[i][j],
							pos=j;
				}
				break;
		}
	}
	Print(n-1,pos); //从最后的状态递归输出
	return 0;
}

 

 


 

The End

Thanks for reading!

- Lucky_Glass

(Tab:如果我有没讲清楚的地方可以直接在邮箱lucky_glass@foxmail.com email我,在周末我会尽量解答并完善博客~📃)

 

posted @ 2018-08-18 13:41  Lucky_Glass  阅读(231)  评论(0编辑  收藏  举报
TOP BOTTOM