意甲冠军:给定的长度可达1000数的顺序,图像password像锁。可以上下滑动,同时会0-9周期。
每个操作。最多三个数字连续操作。现在给出的起始序列和靶序列,获得操作的最小数量,从起始序列与靶序列。
花了一天的时间。我觉得是道非常难的DP。这个阶段非常好划分,对于前面完毕的password锁就不再考虑。问题的关键是这个旋转每次能够的情况非常多。
同一时候也能够发现当I位置上确定移好后,至多影响到后两位。-> dp[i][j][k] 表示当前i位移好后,i+1 为j , i+2为k 的次数。
这里同一时候使用一个预处理技巧,对于一个移动操作。有向上向下两种。
当将一个值转到还有一个值,up[i][j]=(j-i+10)%10;down[i][j]=(i-j+10)%10; i->j 的方法
在dp中必定会有对于阶段的描写叙述,一般从1開始,表达每一段。这里为了求dp[n][0][0] 在最后再补上两位。
dp[0][s[0]][s[1]]=0;刚開始0阶段,一个都未完毕的时候。即前0次移好i+1,i+2位为这种次数为0
int r=up[j][t[i-1]];//① dp[i][(k+u)%10][(s[i+1]+v)%10]=min(...)//②这两句话是我刚開始一直困惑的地方。
首先要明白转移方程是阶段间的转移dp[i-1][...][...]->dp[i][...][...]
这样看,配合②我们发现当dp[i-1][j][k]已知,dp[i][...][...]是将其 j转为t[i-1]。(这个是理解的关键)。这里的u,v表明的是在j转为t[i-1]过程中。后两位的改变情况枚举值。
dp[i][(k+u)%10][(s[i+1]+v)%10] 这个如今就能够理解了,对于dp[i-1]的j 如今dp[i]考虑的就是上次的K了。
另一点就是标号非常乱一会是阶段的i。一会又是起始位置.......
#include <stdio.h> #include <string.h> #include <map> #include <iostream> #define inf 0x3fffffff const int maxn=1010; typedef unsigned __int64 ull; using namespace std; char s[maxn],t[maxn]; int dp[maxn][10][10]; int up[10][10],down[10][10]; int min(int a,int b) { return a<b?a:b; } void init() { int i,j; for(i=0;i<10;i++) for(j=0;j<10;j++) { up[i][j]=(j-i+10)%10; down[i][j]=(i-j+10)%10; } } int main() { init(); while(~scanf("%s%s",s,t)) { memset(dp,0x3f,sizeof(dp)); int i,j,k,u,v; int n=strlen(s); s[n]=s[n+1]=t[n]=t[n+1]=0; for(i=0;i<n;i++) s[i]-='0',t[i]-='0'; dp[0][s[0]][s[1]]=0;//除了最初的序列。其它的dp都没有合法值 for(i=1;i<=n;i++) { for(j=0;j<10;j++)//枚举两位数字的每一位 { for(k=0;k<10;k++) { int r=up[j][t[i-1]];//从j转到t[i-1]的次数,将三位中最左边的数字向上转到目标序列须要的操作 for(u=0;u<=r;u++)//枚举可能出现的操作 { for(v=0;v<=u;v++) dp[i][(k+u)%10][(s[i+1]+v)%10]=min(dp[i][(k + u) % 10][(s[i+1] + v) % 10], dp[i-1][j][k] + r); } r=down[j][t[i-1]]; for(u=0;u<=r;u++) for(v=0;v<=u;v++) dp[i][(k-u+10)%10][(s[i+1]-v+10)%10]=min(dp[i][(k - u+10) % 10][(s[i+1] - v +10) % 10], dp[i-1][j][k] + r); } } } printf("%d\n",dp[n][0][0]); } return 0; }