问题如下......
鲍勃的小妹妹爱丽丝已经九岁了。鲍勃正在测试她的数学实力,要求她计算一个数除以9时给出的余数。
今天,鲍勃向爱丽丝提出了N个问题。我们将问题编号为0到N-1。在每个问题中,Bob都给了Alice相同的M位数字。 (请注意,Bob的号码允许有一些前导零。)
在某些情况下,Alice在阅读该号码时可能会跳过部分数字。然而,她在计算中从未犯过任何其他错误。例如,如果Bob给爱丽丝的号码为012345三次,她可能第一次将其读为0145,第二次读为012345,第三次为135。然后,她的答案是145模9 = 1,12345模9 = 6,135模9 = 0。
您将获得带有M个元素的int N和一个int [] d。对于每个i,数字d [i]对应于Bob的数字中的数字10 ^ i的数字。对于每个i和j,Alice在回答问题j时读取数字i,当且仅当数字d [i]的位数j为1时。
例如,假设d [3] = 6.在二进制中,6是110.换句话说,二进制数字0,1和2是0,1和1.因此,Alice跳过相应的问题0中的数字,但她在问题1和2中阅读。
在今天的实验中发生了一件令人惊讶的事情:对于N个问题中的每个问题,Alice的回答是余数是0. Bob发现这很有趣。他现在想知道:给定N和d,有多少个不同的M位数字有这个属性?
让X成为鲍勃问题的答案。计算并返回值(X modulo 1,000,000,007)。
在考虑了解决方案已经有一段时间了,并且不知道出现之后,我参考了下面的社论
添加,不要乘以
当问题要求你专门使用模9做事时(除以9除外),值得知道一个关于模9和数字的特殊属性。 10≡1mod9,这意味着10的所有幂也是1模9。模数运算将使得如果你取一个数字,快速获得模数9的数字是首先添加其数字然后取余数除以9.例如:4671mod9≡(4⋅1000+6⋅100+7⋅10+ 1)mod9≡4⋅1+6⋅1+7⋅1+1⋅1mod9≡4+ 6 + 7 + 1。
N个模数
让我们考虑从0到M-1的顺序决定每个数字。有N个问题,每个问题代表模数9的一些数字的总和。我们需要每个问题的最终总和为0,但在填写每个数字时,当前总和可能会有所不同并且是不同的到0。
让我们把这看作是一个国家问题。我们从空的M位数字开始,我们希望填写它。最初,N个问题中每个问题的所有数字总和都是0.想象一下,我们决定索引为0的数字将为7.这意味着一些总和s将变为(s + 7)mod9。我们不需要记住整个总和,只需要它的模9。其他一些金额将保持为0.这取决于具体问题的总和中是否包含0。
这允许我们考虑像f(s0,s1,...,sN-1,p)这样的重现。这将为我们提供填充索引大于或等于p的数字的方式的数量,这样si是每个问题的当前总和。
基本情况:p = M,这意味着我们已经用完了数字。我们不再需要添加任何数字,并且总和不能再改变,所有si必须等于0。 否则,我们可以尝试将每个数字i作为将在数字的位置p中使用的数字,并查看状态如何变化。新的总和将是s' 0,s' 1,... s' N-1并且它们将根据数字#p是否包括在相应的总和中而添加i。填充剩余位置的方式的数量是:f(s' 0,s' 1,... s' N-1,p + 1)。我们需要为我们尝试的每个数字添加此结果。 这种复发解决了这个问题。它是非循环的,州的规模不是很大。注意,每个si可以是9个数字中的一个(结果模9),因此最多有95种不同的si组合。 p是O(M),M≤20。对于每个州,我们需要尝试9位数并更新5个总和。如果我们使用memoization或迭代地实现函数,最坏的情况将看起来像95⋅20⋅9⋅5,这非常适合时间限制。
由于参数的灵活性,实现memoization可能会很复杂。我们可以通过两种方式解决这个问题。一个假设总有5个问题,但是5-M的问题忽略了所有数字。
另一种是使用base-9编码来表示状态。
这与使用位掩码(基数2)非常类似,但使用基数 9。
我理解几乎所有内容,包括动态编程状态的选择。但我无法理解的是在解决方案中使用了面具。有人可以解释它是如何完成的,即掩码的实现。
以下是编辑中提供的代码
const int MOD = 1000000007;
int pow9[6];
int N;
vector<int> d;
long dp[9*9*9*9*9][21];
long f(int mask, int p)
{
long & res = dp[mask][p];
if (res == -1) {
res = 0;
if (p == d.size() ) {
// base case
if (mask == 0) {
// good
res = 1;
}
} else {
// pick a digit for the number
for (int i = 0; i <= 9; i++) {
// calculate the new mask:
int mask2 = 0;
for (int j = N-1; j >= 0; j--) {
int o = (mask / pow9[j]) % 9;
if ( (d[p] & (1<<j)) != 0 ) {
o = (o + i) % 9;
}
mask2 = mask2 * 9 + o;
}
res += f(mask2, p+1);
}
res %= MOD;
}
}
return res;
}
int count(int N, vector<int> d)
{
this->d = d;
this->N = N;
memset(dp, -1, sizeof(dp));
pow9[0] = 1;
for (int i = 1; i <= N; i++) {
pow9[i] = 9 * pow9[i-1];
}
return (int)f(0,0);
}
注意:我稍微了解了一下掩码,我在子集求和问题中使用了它们。虽然它与这个实现完全不同。
任何帮助将不胜感激。提前谢谢!