【杂题总汇】Codeforces-67A Partial Teacher
【Codeforces-67A】Partial Teacher
上周刷了一大堆小紫薯的动态规划的题😏……老师给我们布置了一场测试,感觉还好吧……虽然我并不是这么擅长动态规划😜
◇ 题目
<手写翻译>
老师要给n个学生发糖,n个学生排成一队。由于老师有偏见🤨,对于一对相邻的学生,他会给成绩好的学生多一些糖(严格大于),而成绩好的学生少一些糖(严格小于),若相邻学生成绩相等,那么他们得到的糖的数量也是一样的。每个学生都至少得到一颗糖。求老师最少总共要给学生多少糖,输出给最少的糖的方案。
<输入输出>
单组数据。第一行输入n,表示学生的数量,学生编号为1~n,且1~n从左到右排列。第二行一个字符串描述了相邻学生的成绩关系,第i个字符描述的是第i个学生和和第i+1个学生的成绩关系,L表示第i个学生的成绩更好,R表示第i+1个学生的成绩更好,=表示两人成绩相等。
<样例&没解释🙃>
第一组数据 | 第二组数据 | ||
Input | Output | Input | Output |
5 |
2 1 2 1 2 |
5 |
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