UVa1631

题意:

有一个n(n≤1000)位密码锁,每位都是0~9,可以循环旋转。每次让1~3个相邻数字同时往上或者往下转一格,567890->567901(最后3位向上)。输入初始状态和终止状态(长度不超过1000),问最少要转几次。

这道题刚开始看有点蒙,不知道应该如何转密码锁上面的格,然后通过经验能够感觉出来最优的转动方案是可以按照

从左到右的顺序来移动到正确的位置的,这样我们将无序的转动变成了有序的转动,自然问题就简化了许多,

然后通过观察题意(每次让1~3个相邻数字同时往上或者往下转一格),我发现1-3个相邻的数字说明了如果按照从左到右的

顺序进行转动后那么可以仅仅影响前面或后面1-3个格子的,然后发现影响后面的格子会更好写程序,所以下面是状态与转移方程:
  d(i,j,k,p)表示前i-1个已经配对,其中i、i+1、i+2分别对应j、k、p还需要最少波动次数
  状态转移方程:其中第i个一定要拨动使之配对,其余的i+1、i+2可以随着i的拨动进行改变(也可以不改变),可以往上下拨。
  最后答案为d(0,a[0],a[1],a[2])

这需要使用记忆化搜索求解,刚开始我拨动的时候犯了一个错误,如果i拨动x次,那么i+1、i+2也一定拨动x次,或者仅仅i+1拨动x次,

这样其实是错误的(想一想,为什么),后面拨动的次数是任意的(但需要保证小于前面拨动次数),下面是代码:

// UVa 1631 
/*
  d(i,j,k,p)表示前i-1个已经配对,其中i、i+1、i+2分别对应j、k、p还需要最少波动次数
  状态转移方程:其中第i个一定要拨动使之配对,其余的i+1、i+2可以随着i的拨动进行改变(也可以不改变),可以往上下拨。
  最后答案为d(0,a[0],a[1],a[2])
*/
#include <cstdio> 
#include <cstring> 
#include <algorithm>
using namespace std; 

const int maxn = 1000 + 10; 
const int INF = 1000000003; 

char s[maxn], s2[maxn];
int len, a[maxn], b[maxn]; 
int d[maxn][11][11][11]; 

int dp(int cur, int x, int y, int z) { 
  if (cur >= len) return 0; 
  
  int& ans = d[cur][x][y][z]; 
  if (ans >= 0) return ans; 
  
  ans = INF;
  int down = (b[cur]+10-x)%10, up = (x+10-b[cur])%10;
  for (int i = 0; i <= down; ++i) 
    for (int j = 0; j <= i; ++j) 
      ans = min(ans, dp(cur+1,(y+i)%10,(z+j)%10,a[cur+3])+down);
  for (int i = 0; i <= up; ++i) 
    for (int j = 0; j <= i; ++j) 
      ans = min(ans, dp(cur+1,(y+10-i)%10,(z+10-j)%10,a[cur+3])+up);
  return ans; 
}

int main() { 
  while (scanf("%s%s", s, s2) == 2) {
      len = strlen(s); 
      for (int i = 0; i < len; ++i) a[i] = s[i] - '0'; 
      for (int i = 0; i < len; ++i) b[i] = s2[i] - '0';
      a[len] = a[len+1] = a[len+2] = b[len] = b[len+1] = b[len+2] = 0;   
      memset(d, -1, sizeof(d)); 
      printf("%d
", dp(0,a[0],a[1],a[2]));
  }
  return 0;
}
原文地址:https://www.cnblogs.com/yifeiWa/p/11330489.html