[UOJ 74] 【UR #6】破解密码

时间:2023-03-09 16:08:44
[UOJ 74] 【UR #6】破解密码

题目链接:UOJ - 74

题目分析

题目中,将字符串 S 的第一个字符移到末尾,其他字符向前移动一个位置,f(S) 就从 Hi 变成了 Hi+1。

我们分析一下这个过程:假设第一个字符为 c, (Hi - 26^(n-1) * c) * 26 + c = Hi+1

26 * Hi - Hi+1 = (26^n - 1) * c

c = (26 * Hi - Hi+1) * Inv(26^n - 1)

那么每一个 c 都可以直接解出来,由于题目保证有解,所以每一个 c 解出来的一定都是 [0, 25] 的数。

嗯..看起来非常对..提交上去....WA.50

为什么只得了50分呢?因为:“有除法的时候一定要考虑没有逆元的情况!

当 (26^n - 1) % p = 0 的时候,26^n - 1 是没有逆元的。而这样的点在数据里有 5 个 【QAQ】。

这样在前面的方程 26 * Hi - Hi+1 = (26^n - 1) * c 中含有 c 的项系数为 0,被消去,变成了 Hi * 26 = Hi+1 。

因为方程与 c 无关,所以 c 是任何一个字母这个方程都可以成立。

那么就要根据 f(S) = Hi 来求出这个 S 了,我们知道 f(S) 相当于是把字符串看做一个 26 进制数,然后这里的 Hi * 26 = Hi+1。

所以我们把 H0 看做26进制数求出对应的字符串,之后向左移一位就相当于将 H 乘了 26 ,依然是符合 f(S) = Hi+1 的。

代码

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm> using namespace std; const int MaxN = 100000 + 5; typedef long long LL; int n, p, t;
int H[MaxN]; LL A0, Temp;
LL X[MaxN]; LL Pow(LL a, int b)
{
LL ret = 1, f = a;
while (b)
{
if (b & 1)
{
ret *= f;
ret %= p;
}
b >>= 1;
f *= f;
f %= p;
}
return ret;
} LL NY(LL x)
{
x = ((x % p) + p) % p;
return Pow(x, p - 2);
} int main()
{
scanf("%d%d", &n, &p);
for (int i = 0; i < n; ++i) scanf("%d", &H[i]);
if (Pow(26ll, n) != 1)
{
Temp = NY(Pow(26ll, n) - 1);
for (int i = 0; i < n; ++i)
{
if (i == n - 1) t = 0;
else t = i + 1;
X[i] = ((LL)H[i] * 26 - (LL)H[t]) % p * Temp;
X[i] = ((X[i] % p) + p) % p;
}
}
else
{
int Pos = n;
while (H[0])
{
X[--Pos] = H[0] % 26;
H[0] /= 26;
}
}
for (int i = 0; i < n; ++i) printf("%c", 'a' + X[i]);
printf("\n");
return 0;
}