标签:记忆化 return 就是 第一个 背景 思路 博客 还需要 oid
题目大意:
背景为弹钢琴,需要弹的钢琴琴键顺序为序列a(数字序列),弹钢琴只用一只手,五根手指从左到右标号1-5。设序列b为按键顺序,bi代表第i个键使用标号为bi的手指。aibi一一对应,当且仅当以下三种情况时,我们称b序列是合适的:
对于任意1<=i<=n-1:
ai<ai+1时,bi<bi+1
ai>ai+1时,bi>bi+1
ai=ai+1时,bi≠bi+1
要求求出一个合法的b序列,或者确定求不出
n<=105,ai<=2*105
解题思路(菜鸡瞎想):
一开始看到的时候,我觉得这应该就是个贪心,因为观察到样例给的答案里第一个数字不是1就是5,所以可能就有在某种情况尽可能用小的,在另一种情况尽可能用大的这样。
但实际操作发现是可以举出反例的...
后来看了dalao的博客,发现,对于每个bi,只有5中情况,虽然5n的复杂度不可忍受,但5n可以呀!
于是,思路就定为:记忆化搜索或者DP
表示状态:dp[i][j]表示第i个琴键用j手指能否弹完剩下的曲子
那么,我们就可以从n-1倒着搜(因为最后一个随便哪个手指都是合法的):
每搜到一个,根据上面的情况,从比当前键大/小/不等于的键中选一个键k,k满足dp[i+1][k]=true
也就是说当前键之后要弹得的那个键必须是可用的。
我们还需要一个ans[i][j]表示第i个键弹j如果可以的话之后要弹哪个,即ans i j =k用于回溯答案
最后,我们从dp[1]中选一个可用的,沿着ans一路返回到dp[n],边回溯边输出答案
以下是代码:
#include <cstdio> int N,a[100005]; int ans[100005][6]; bool dp[100005][6]; void print(int x){ int i=1; while(x>0){ printf("%d ",x); x=ans[i++][x]; } } int main(){ scanf("%d",&N); for(int i=1;i<=N;i++)scanf("%d",&a[i]); for(int i=1;i<=5;i++)dp[N][i]=true; for(int i=N-1;i;i--){ for(int j=1;j<=5;j++){ if(a[i]<a[i+1]){ for(int k=j+1;k<=5;k++){ if(dp[i+1][k]){ dp[i][j]=true; ans[i][j]=k; break; } } } else if(a[i]>a[i+1]){ for(int k=1;k<j;k++){ if(dp[i+1][k]){ dp[i][j]=true; ans[i][j]=k; break; } } } else if(a[i]==a[i+1]){ for(int k=1;k<=5;k++){ if(k==j)continue; if(dp[i+1][k]){ dp[i][j]=true; ans[i][j]=k; break; } } } } } for(int i=1;i<=5;i++){ if(dp[1][i]){ print(i); return 0; } } printf("-1"); return 0; }
标签:记忆化 return 就是 第一个 背景 思路 博客 还需要 oid
原文地址:https://www.cnblogs.com/Zarax/p/12267908.html