问题:935. 骑士拨号器

国际象棋中的骑士可以按下图所示进行移动:

 .           

这一次,我们将 “骑士” 放在电话拨号盘的任意数字键(如上图所示)上,接下来,骑士将会跳 N-1 步。每一步必须是从一个数字键跳到另一个数字键。

每当它落在一个键上(包括骑士的初始位置),都会拨出键所对应的数字,总共按下 N 位数字。

你能用这种方式拨出多少个不同的号码?

因为答案可能很大,所以输出答案模 10^9 + 7

示例 1:

输入:1
输出:10

示例 2:

输入:2
输出:20

示例 3:

输入:3
输出:46

提示:

  • 1 <= N <= 5000

链接:https://leetcode-cn.com/contest/weekly-contest-109/problems/knight-dialer/

分析:

如果只拨号1次,10个数字都有可能,结果返回10,

如果1次以上,不可能有5,而且其他9个数字相互之间可达

建立直接的goto表,

gototables[0] = vector < int > {4, 6};
gototables[1] = vector < int > {8, 6};
gototables[2] = vector < int > {7, 9};
gototables[3] = vector < int > {4, 8};
gototables[4] = vector < int > {3, 9, 0};
gototables[5] = vector < int > {};
gototables[6] = vector < int > {1, 7, 0};
gototables[7] = vector < int > {2, 6};
gototables[8] = vector < int > {1, 3};
gototables[9] = vector < int > {2, 4};

第一个gototables[0] = vector < int > {4, 6};表示从0可以直接到达4 6,反之,可以从4 6 到达0.

拨第K次的号码数取决于前一次0-9(5除外,或者5一直都是0)的个数。

比如拨3次的号码数,取决于拨第二次后的0-9个数,

用公式表示的话,

goto[i]=T[m,n...]

i表示一个数组,T是一个数字,里面的m、n,和i可以互相到达

Num(i,j)表示拨号j次后i的个数

则Num(i,j)=ΣT(T(i),j-1)

前面几次结果如下表:

  可达列表 数字 1(次数) 2 3 4
    6 4 0 1 2 6(0对应4 6,前一次值均为3) 12
    8 6 1 1 2 5(1对应8 6,前一次值2 3) 10
    9 7 2 1 2 4 10
    8 4 3 1 2 5 10
  9 3 0 4 1 3 6 16
        5 1      
  7 1 0 6 1 3 6 16
    6 2 7 1 2 5 10
    3 1 8 1 2 4 10
    4 2 9 1 2 5 10
        总和 10 20 46  


AC Code:

class Solution935
{
public: map<int, vector<int> > gototables = {}; //从一个键可以到达的键的表
long long Mod = 1000000007;
int knightDialer(int N)
{
init();
if (N == 1)
{
return 10;
}
long long ret=0;
vector<long long> nums; //记录拨号第M次后各个数字的个数
for (int i = 0; i < 10; i++)
{
nums.emplace_back(0);
}
for (int i = 1; i < N; i++)
{
if (i == 1)
{
for (int j = 0; j < nums.size(); j++) //第一次拨号后的每个数字个数
{
nums[j] += this->gototables[j].size();
}
}
else
{
vector<long long> pre; //记录前一次的数字个数,用来更新本次拨号后的未位各个数字个数
for (int t = 0; t < nums.size(); t++)
{
pre.emplace_back(nums[t]);
}
for (int j = 0; j < nums.size(); j++)
{
vector<int> tmp = this->gototables[j]; //该位数字可以按到的下一个,同时也是可以按到该位数字的前一个数字
long long local = 0;
for (int m = 0; m < tmp.size(); m++)
{
local += pre[tmp[m]]; //正好用0-9下标存储对应数字的个数,直接累加即可
}
local %= this->Mod; //余数定理可以先求余后累加
nums[j] = local; //得到该位数字的个数
}
}
}
ret = 0;
for (int i = 0; i < nums.size(); i++)
{
ret += nums[i]; //各个数字为最终尾数的个数和即为结果
}
ret %= this->Mod;
return ret;
} void init()
{
gototables[0] = vector < int > {4, 6};
gototables[1] = vector < int > {8, 6};
gototables[2] = vector < int > {7, 9};
gototables[3] = vector < int > {4, 8};
gototables[4] = vector < int > {3, 9, 0};
gototables[5] = vector < int > {};
gototables[6] = vector < int > {1, 7, 0};
gototables[7] = vector < int > {2, 6};
gototables[8] = vector < int > {1, 3};
gototables[9] = vector < int > {2, 4};
}
};  

其他:

1.最开始通过递归来做,深度优先模拟拨号过程,结果超时

2.没认真考虑,单纯优化为能够到达下一次的个数,即初始能按下两个键的有七个,能按下3个键的有2个,第二次后能按下两个键的有7*2个,能按下三个键的有2*3个,结果20也是符合,第三次7*2*2=28,2*3*3=18,28+18=46也符合,结果错了......,一来第四次后测试太多不方便验证,二来得出结论太草率,实际上按后尾号为某个数字的个数取悦于前一次能按过来的数字个数和。

3.这个问题折腾了一个多小时,关键在于建立的模型不对,而且手动验证太麻烦,总是出错重来,思维不够清晰。

第一的code:

typedef long long ll;
typedef vector<int> VI;
typedef pair<int,int> PII; #define REP(i,s,t) for(int i=(s);i<(t);i++)
#define FILL(x,v) memset(x,v,sizeof(x)) const int INF = (int)1E9;
#define MAXN 100005 VI adj[10];
const int mod = 1000000007;
int dp[5005][10];
class Solution {
public:
int knightDialer(int N) {
adj[1] = VI({6,8});
adj[2] = VI({7,9});
adj[3] = VI({4,8});
adj[4] = VI({0,3,9});
adj[6] = VI({0,1,7});
adj[7] = VI({2,6});
adj[8] = VI({1,3});
adj[9] = VI({2,4});
adj[0] = VI({4,6});
FILL(dp, 0);
REP(d,0,10) dp[1][d] = 1;
REP(i,2,N+1) {
REP(d,0,10) {
int pre = dp[i-1][d];
if (pre == 0) continue;
REP(k,0,adj[d].size()) {
int d2 = adj[d][k];
dp[i][d2] = (dp[i][d2] + pre) % mod;
}
}
}
int ans = 0;
REP(d,0,10) ans = (ans + dp[N][d]) % mod;
return ans;
}
};

看到code中的dp,想到之前有刻意针对动态规划做过练习,结果用的时候还是没有这个意思,没学好。

LeetCode935的更多相关文章

  1. [Swift]LeetCode935. 骑士拨号器 | Knight Dialer

    A chess knight can move as indicated in the chess diagram below:  .            This time, we place o ...

随机推荐

  1. CC20:高度最小的BST

    题目 对于一个元素各不相同且按升序排列的有序序列,请编写一个算法,创建一棵高度最小的二叉查找树. 给定一个有序序列int[] vals,请返回创建的二叉查找树的高度. 解法 这道题感觉如果没有创建树的 ...

  2. Codeforces 548E(莫反、容斥)

    转化为质数域上的操作,如果用莫反的话,记录因数的cnt. 其实莫反的推式子最后和容斥做法殊途同归了,容斥的系数就是莫比乌斯函数. const int maxn = 2e5 + 5, maxa = 5e ...

  3. css中的各类问题

    1.水平垂直居中 一.水平居中 (1)行内元素解决方案 只需要把行内元素包裹在一个属性display为block的父层元素中,并且把父层元素添加如下属性即可: .parent { text-align ...

  4. Uva11572

    读入可以不需要存入数组 #include<bits/stdc++.h> #define inf 0x3f3f3f3f //const int maxn=; using namespace ...

  5. Yii2.0权限系统,使用PhpManager的方式

    网上搜了一大堆yii2.0权限系统,大抵都是千篇一律,而且基本上都是DbManager.看了半天官方文档之后,终于知道了PhpManager的方式下,是怎么引入权限系统.介绍下我自己的使用.首先,配置 ...

  6. P4876 近似排列计数50

    时间限制:1s 内存限制:256MB [问题描述] 对于一个1-n的排列,如果满足第i个数|ai-i|<=k,则称该排列为K-近似排列. 现在排列的若干位置已经确定,你需要计算剩下的数有多少种排 ...

  7. mysql 中unsigned

    整型的每一种都分有无符号(unsigned)和有符号(signed)两种类型(float和double总是带符号的),在默认情况下声明的整型变量都是有符号的类型(char有点特别),如果需声明无符号类 ...

  8. 常用快捷键—Webstorm

    常用快捷键—Webstorm入门指南 提高代码编写效率,离不开快捷键的使用,Webstorm拥有丰富的代码快速编辑功能,你可以自由配置功能快捷键. 快捷键配置 点击“File”-> “setti ...

  9. Kendo MVVM 数据绑定(三) Click

    Kendo MVVM 数据绑定(三) Click Click 绑定可以把由 ViewModel 定义的方法不绑定到目标 DOM 的 click 事件.当点击目标 DOM 元素时触发 ViewModel ...

  10. 桉树IAAS云架构(转载)

    您可在 IaaS 云中建立和管理混合多虚拟机集群环境,并将现有 vSphere™. ESX™.ESXi™.KVM 和 XEN 虚拟环境作为 AWS 兼容 Eucalyptus桉树混合云管理.现在 Eu ...