[SDOI2008]Sandy的卡片 题解
题目地址:ė …
May all the beauty be blessed.
KMP是一种字符串匹配算法,其复杂度已经达到了该类算法的下界,即O(|T|+|P|),其中T是文本串,P是模式串。下面介绍它的原理与实现。
下面我们介绍一种被称作MP算法的东西,这可以说是KMP算法的一个前身。
我们来尝试用P=”ABCD”来匹配T=”ABCABCABCD”,并观察它的过程。
第一步:P_0 \rightarrow T_0
ABCABCABCD |||X ABCD
第二步:P_0 \rightarrow T_1
ABCABCABCD X ABCD
第三步:P_0 \rightarrow T_2
ABCABCABCD X ABCD
第四步:P_0 \rightarrow T_3
ABCABCABCD |||X ABCD
……
第七步:P_0 \rightarrow T_6
ABCABCABCD |||| ABCD
这便是朴素地匹配字符串的过程,我们发现要匹配完成一共尝试了7次。这个算法的复杂度是O(|T||P|)的。
下面我们想,如果在第一步P_3失配后能够直接转移到第四步的位置,因为我们会发现用T_1, T_2去匹配P是完全没有用的。我们需要知道,如果一个字符失配了,应该将P后移到哪里才能避免中间若干失配的情况。我们把这个信息表示为fail[i]失配数组,具体而言,失配数组表示如果当前位置失配了,应该将P的哪一位移到这里来。
我们会发现,失配需要移动的时候实际上是从当前位置之前的子串中找到一个最长的后缀,使得其与某一前缀相等。这个过程可以用自我匹配来完成。
inline void calfail() {
int i = 0, j = -1;
fail[0] = -1;
for(; pattern[i]; i++, j++) {
while(j >= 0 && pattern[j] != pattern[i]) {
j = fail[j];
}
fail[i + 1] = j + 1;
}
}
从当前位置开始往前的某个后缀是原串的某个前缀,那么后一位如果失配应该移至这个前缀的后一位。
举个例子,如果P=”ABABC”,fail数组应该看起来像这样
P= A B A B C fail=-1 0 0 1 2
当我们发现没有这样的后缀时,我们会到达P的头,得到fail[0]=-1这个值,这意味着我们需要将P整体后移了。现在我们匹配的思路也就明确了,即失配→用失配数组中的上一个位置对齐继续匹配,直到匹配完成。
下面就是一个匹配的实现。
inline int match() {
calfail();
int i = 0, j = 0, m = strlen(pattern);
for(; text[i]; i++, j++) {
while(j >= 0 && pattern[j] != text[i]) {
j = fail[j];
}
if(j >= m - 1) {
return i - m + 1;
}
}
}
现在的算法就是O(|P|+|T|)的了。
我们来观察一组MP的过程。现在T=”ABCABCABABC”,P=”ABABC”。
第一步:P_0 \rightarrow T_0
ABCABCABABC ||X ABABC
第二步:P_0 \rightarrow T_2
ABCABCABABC X ABABC
第三步:P_0 \rightarrow T_3
ABCABCABABC ||X ABABC
第四步:P_0 \rightarrow T_5
ABCABCABABC X ABABC
第四步:P_0 \rightarrow T_6
ABCABCABABC ||||| ABABC
我们发现第二步、第四步的时候,我们老是在C这个位置失配,每次失配还要尝试2次,既然我们都知道了C和现在与fail指定的位置的A配不上,为什么不想办法把这段给跳过去呢?
接下来我们会更改fail的求法,使得中间重复的A被跳过去,实际上更改的方法也很简单。
inline void calfail() {
int i = 0, j = -1;
fail[0] = -1;
for(; pattern[i]; i++, j++) {
while(j >= 0 && pattern[j] != pattern[i]) {
j = fail[j];
}
fail[i + 1] = pattern[j + 1] == pattern[i + 1] ? fail[j + 1] : j + 1;
// 如果遇到了相同的字符,再往前跳一次
}
}
匹配的方法与上面MP的一样,只是fail有一点小区别而已。这个优化并没有影响整体复杂度,只是一个常数优化。
注意我们求出来的fail数组实际上比P串长一位,最后一位的值并没有用上。
本代码可以通过【P3375】【模板】KMP字符串匹配 – 洛谷,一题。
// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>
const int MAXN = 1000005;
int fail[MAXN];
char str1[MAXN], str2[MAXN];
inline void calfail() {
int i = 0, j = -1;
fail[0] = -1;
for(; str2[i]; i++, j++) {
while(j >= 0 && str2[j] != str2[i]) {
j = fail[j];
}
fail[i + 1] = j + 1;
}
}
inline void match() {
calfail();
int i = 0, j = 0, m = strlen(str2);
for(; str1[i]; i++, j++) {
while(j >= 0 && str2[j] != str1[i]) {
j = fail[j];
}
if(j >= m - 1) {
printf("%d\n", i - m + 2);
}
}
}
int main() {
scanf("%s%s", str1, str2);
match();
for(int i = 1; str2[i - 1]; i++) {
printf("%d ", fail[i]);
}
return 0;
}
// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>
const int MAXN = 1000005;
int fail[MAXN];
char str1[MAXN], str2[MAXN];
inline void calfail() {
int i = 0, j = -1;
fail[0] = -1;
for(; str2[i]; i++, j++) {
while(j >= 0 && str2[j] != str2[i]) {
j = fail[j];
}
fail[i + 1] = str2[j + 1] == str2[i + 1] ? fail[j + 1] : j + 1;
}
}
inline void match() {
calfail();
int i = 0, j = 0, m = strlen(str2);
for(; str1[i]; i++, j++) {
while(j >= 0 && str2[j] != str1[i]) {
j = fail[j];
}
if(j >= m - 1) {
printf("%d\n", i - m + 2);
}
}
}
int main() {
scanf("%s%s", str1, str2);
match();
for(int i = 0; str2[i]; i++) {
printf("%d ", fail[i]);
}
return 0;
}
一整数a对同余n之模逆元是满足以下公式的整数b
a^{-1} \equiv b \pmod{n}
也可以写成
ab \equiv 1 \pmod{n}
费马小定理可以转换成如下形式
若a是一个整数,p是一个质数,则
a^{p-2} \equiv a^{-1} \pmod{p}
我们设p=ki+r, r < i, 1 < i < p,然后把左边的式子展开
ki + r \equiv 0 \pmod{p}
两边同乘 (ir)^{-1}
\begin{aligned} kr^{-1} + i^{-1} &\equiv 0 &\pmod{p} \\ i^{-1} &\equiv -kr^{-1} &\pmod{p} \\ i^{-1} &\equiv -\lfloor \frac{p}{i} \rfloor \cdot (p \bmod i)^{-1} &\pmod{p} \end{aligned}
于是我们就得到了逆元的线性递推式:
inv[i] = -(MO / i) * inv[MO % i];
先处理 (n!)^{-1} ,然后 [(n-1)!]^{-1} \equiv n(n!)^{-1} \pmod{p} 。
题目地址:洛谷:【P4245】【模板】MTT – 洛谷
多项式乘法。
输入样例#1:
5 8 28 19 32 0 182 99 95 77 54 15 3 98 66 21 20 38
输出样例#1:
7 18 25 19 5 13 12 2 9 22 5 27 6 26
数据范围1e5。
由于NTT对模数有要求,我们需要使用任意模数卷积算法,也就是俗称的MTT。具体做法是,把一个数拆成32768x+y的形式,对拆出来的四组数分别卷积后拼起来。这样可以避免计算过程中的越界问题,一般的数据能够满足精度。
单位根最好预处理出来,这样才不会有精度问题。
// Code by KSkun, 2018/3
#include <cstdio>
#include <cmath>
#include <algorithm>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF
: *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
char c = fgc();
while(c < '0' || c > '9') {
if(c == '-') neg = -1;
c = fgc();
}
while(c >= '0' && c <= '9') {
res = res * 10 + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 1 << 20;
const double PI = acos(-1);
int MO;
struct Complex {
double real, imag;
Complex(double real = 0, double imag = 0) : real(real), imag(imag) {}
inline Complex conj() {
return Complex(real, -imag);
}
inline Complex operator+(Complex rhs) const {
return Complex(real + rhs.real, imag + rhs.imag);
}
inline Complex operator-(Complex rhs) const {
return Complex(real - rhs.real, imag - rhs.imag);
}
inline Complex operator*(Complex rhs) const {
return Complex(real * rhs.real - imag * rhs.imag, imag * rhs.real + real * rhs.imag);
}
inline Complex operator*=(Complex rhs) {
return (*this) = (*this) * rhs;
}
friend inline Complex operator*(double x, Complex cp) {
return Complex(x * cp.real, x * cp.imag);
}
inline Complex operator/(double x) const {
return Complex(real / x, imag / x);
}
inline Complex operator/=(double x) {
return (*this) = (*this) / x;
}
friend inline Complex operator/(double x, Complex cp) {
return x * cp.conj() / (cp.real * cp.real - cp.imag * cp.imag);
}
inline Complex operator/(Complex rhs) const {
return (*this) * rhs.conj() / (rhs.real * rhs.real - rhs.imag * rhs.imag);
}
inline Complex operator/=(Complex rhs) {
return (*this) = (*this) / rhs;
}
inline double length() {
return sqrt(real * real + imag * imag);
}
};
int n, m, len, rev[MAXN];
LL x[MAXN], y[MAXN], z[MAXN];
Complex a[MAXN], b[MAXN], c[MAXN], d[MAXN], e[MAXN], f[MAXN], g[MAXN], h[MAXN], wn[MAXN];
inline void fft(Complex *arr, int f) {
for(int i = 0; i < n; i++) {
if(i < rev[i]) std::swap(arr[i], arr[rev[i]]);
}
for(int i = 1; i < n; i <<= 1) {
for(int j = 0; j < n; j += i << 1) {
for(int k = 0; k < i; k++) {
Complex w = Complex(wn[n / i * k].real, f * wn[n / i * k].imag),
x = arr[j + k], y = w * arr[j + k + i];
arr[j + k] = x + y;
arr[j + k + i] = x - y;
}
}
}
if(f == -1) {
for(int i = 0; i < n; i++) {
arr[i] /= n;
}
}
}
int main() {
n = readint(); m = readint(); MO = readint();
for(int i = 0; i <= n; i++) {
x[i] = readint() % MO;
}
for(int i = 0; i <= m; i++) {
y[i] = readint() % MO;
}
m += n;
for(n = 1; n <= m; n <<= 1) len++;
for(int i = 0; i < n; i++) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
}
for(int i = 1; i < n; i <<= 1) {
for(int k = 0; k < i; k++) {
wn[n / i * k] = Complex {cos(PI * k / i), sin(PI * k / i)};
}
}
for(int i = 0; i < n; i++) {
a[i].real = x[i] >> 15; b[i].real = x[i] & 0x7fff;
c[i].real = y[i] >> 15; d[i].real = y[i] & 0x7fff;
}
fft(a, 1); fft(b, 1); fft(c, 1); fft(d, 1);
for(int i = 0; i < n; i++) {
e[i] = a[i] * c[i]; f[i] = b[i] * c[i];
g[i] = a[i] * d[i]; h[i] = b[i] * d[i];
}
fft(e, -1); fft(f, -1); fft(g, -1); fft(h, -1);
for(int i = 0; i < n; i++) {
z[i] = (((LL(round(e[i].real)) % MO) << 30) % MO
+ ((LL(round(f[i].real)) % MO) << 15) % MO
+ ((LL(round(g[i].real)) % MO) << 15) % MO
+ LL(round(h[i].real)) % MO) % MO;
}
for(int i = 0; i <= m; i++) {
printf("%lld ", z[i]);
}
return 0;
}
Copyright © 2017-2022 KSkun's Blog.
Authored by KSkun and his friends.
本博客内所有原创内容采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。引用内容如果侵权,请在此留言。
All original content in this blog is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
If any reference content infringes your rights, please contact us.