locker
Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 248 Accepted Submission(s): 87
Problem Description A password locker with N digits, each digit can be rotated to 0-9 circularly.
You can rotate 1-3 consecutive digits up or down in one step.
For examples:
567890 -> 567901 (by rotating the last 3 digits up)
000000 -> 000900 (by rotating the 4th digit down)
Given the current state and the secret password, what is the minimum amount of steps you have to rotate the locker in order to get from current state to the secret password?
Input Multiple (less than 50) cases, process to EOF.
For each case, two strings with equal length (≤ 1000) consists of only digits are given, representing the current state and the secret password, respectively.
Output For each case, output one integer, the minimum amount of steps from the current state to the secret password.
Sample Input 111111 222222 896521 183995
Sample Output 2 12
Source
2012 Asia Tianjin Regional Contest
Recommend zhoujiaqi2010
这题的意思就相当于是一个数字密码锁。
每次可以正向或者反向旋转连续的1-3个数字。求从现在状态转到目标状态需要的最少步数。
题目给了两个长度一样的由0-9组成的字符串。就相当于每次操作可以选择连续的1-3个数字加1或者减1.这不过这个加和减是循环的。0减1到9,9加1到0.
一看就是DP。
这不过DP方程不好想,也不好表示状态。
dp[i][x][y]表示处理到第i个,后面两个数字是x,y,把前i位转正确需要的最少步数。
计算dp[i][x][y]时,前i-2位是题目给的现在状态的值,第i-1位是x,第i位是y,就是把前i位转正确。
要把dp[i]的状态转移到dp[i-1]去。
把第i位从x转到目标态b[i]去,就可以把状态转移了。
和第i位相关的转动有三种:一是单转第i位,二是转第i位和第i-1位,三是转第i位、第i-1位和第i-2位。
根据三种可以确定 dp[i-1][xx][yy]中的xx,yy;
转动分为正转和反转。
如果第i位是正转,转正确需要d1步。
那么第i-1和第i-2位正转的不是是小于等于d1的。而且i-2的步数小于等于i-1。
如果第i位是正转,转正确需要d2步。
那么第i-1和第i-2位正转的不是是小于等于d2的。而且i-2的步数小于等于i-1。
这样DP的时候i从1到n转移过去。
处理dp[i]的时候,dp[1~(n-1)][0~9][0~9]都是已知的。就很容易确定dp[i][0~9][0~9]的最小值了。
注意处理的是初始化过程,全部初始化为INF,dp[0][0][0]=0;
还有转移的时候,i==1和i==2单独处理下。。
转移最多是1000*100*100。
是可以接受的。
我一开始做的时候增加了以为dp[i][x][y][z]表示了后面三位,好理解,但是TLE了。
减少一维表示就AC了。
具体看代码吧!
注释的很清楚了。
#include<stdio.h> #include<iostream> #include<string.h> #include<algorithm> using namespace std; const int MAXN=1010; const int INF=100000000; char str1[MAXN],str2[MAXN]; int a[MAXN],b[MAXN]; int dp[MAXN][10][10]; //dp[i][x][y]表示处理到第i个,后面两位是xy时的最小操作数 int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int n; int x,y; int xx,yy; int i,j,k; while(scanf("%s%s",&str1,&str2)!=EOF) { n=strlen(str1); for(i=1;i<=n;i++) { a[i]=str1[i-1]-'0'; b[i]=str2[i-1]-'0'; } for(i=0;i<=n;i++) for(x=0;x<10;x++) for(y=0;y<10;y++) dp[i][x][y]=INF; //初始化 dp[0][0][0]=0; for(i = 1 ; i <= n ; i++)//1到n进行DP for(x = 0 ; x < 10 ; x++)//这是第i-1个数 { if(i <= 1 && x > 0) break; for(y = 0 ; y < 10 ; y++)//这是第i个数 { //增加和减少变换 //把第i个数调整正确,需要的步数 int d1 = (b[i] - y + 10)%10;//这是正向转 int d2 = (y - b[i] + 10)%10;//这是逆向转 //下面的DP过程就是把i的状态表示成i-1的状态 if(i==1) { xx=0; yy=0; dp[i][x][y]=min(dp[i][x][y],dp[i-1][xx][yy]+min(d1,d2)); continue; } if(i==2) { xx=0;//i-1的倒数第2位一定是0 //下面是枚举i-1的倒数第一位 for(j=x;j<=x+d1;j++) { yy=j%10; dp[i][x][y]=min(dp[i][x][y],dp[i-1][xx][yy]+d1); } for(j=x;j>=x-d2;j--) { yy=(j+10)%10; dp[i][x][y]=min(dp[i][x][y],dp[i-1][xx][yy]+d2); } continue; } //枚举i-1的倒数第1位和倒数第2位 for(j=0;j<=d1;j++) for(k=j;k<=d1;k++) { xx=(a[i-2]+j)%10; yy=(x+k)%10; dp[i][x][y]=min(dp[i][x][y],dp[i-1][xx][yy]+d1); } for(j=0;j<=d2;j++) for(k=j;k<=d2;k++) { xx=(a[i-2]-j+10)%10; yy=(x-k+10)%10; dp[i][x][y]=min(dp[i][x][y],dp[i-1][xx][yy]+d2); } } } if(n>=2)x=a[n-1]; else x=0; if(n>=1)y=a[n]; else y=0; printf("%d\n",dp[n][x][y]); } return 0; }