2020 ACM-ICPC澳门区域赛 B题 Boring Problem 题解
作者:互联网
B Boring Problem
题意:
给出 n ≤ 100 n \le 100 n≤100个长度为 m ≤ 100 m \le 100 m≤100的串 T i T_i Ti和一个串 R R R,对每个 R R R的前缀,每次在其末尾以 P i P_i Pi的概率添加字符 i ≤ k ≤ 26 i \le k \le 26 i≤k≤26,当生成的字符串 S S S中存在一个 T i T_i Ti为 S S S的子串时停止,求生成的字符串的期望长度。
题解:
对
T
i
T_i
Ti建立AC自动机,设
t
r
i
,
j
tr_{i,j}
tri,j表示从节点
i
i
i添加字符
j
j
j到达的节点,
E
i
E_i
Ei表示从节点
i
i
i开始,停止时末尾添加字符个数的期望。
最后用字符串
R
R
R跑一边AC自动机计算答案。
在AC自动机上,对于所有叶子节点, E l e a f = 0 E_{leaf} = 0 Eleaf=0;对于所有非叶子节点 u u u,转移为 E u = 1 + ∑ i = 1 k E t r u , i P i E_u=1 +\sum_{i=1}^k\frac{E_{tr_{u,i}}}{P_i} Eu=1+∑i=1kPiEtru,i;注意到转移有环,需要高斯消元, O ( n m ) O(nm) O(nm)个未知数和等式,复杂度 O ( n 3 m 3 ) O(n^3m^3) O(n3m3)。
将未知数个数降为
O
(
n
)
O(n)
O(n):
设
E
q
u
a
t
i
o
n
i
Equation_i
Equationi表示
E
i
=
E
q
u
a
t
i
o
n
i
E_i=Equation_i
Ei=Equationi,其中
E
q
u
a
t
i
o
n
i
Equation_i
Equationi由常数和
O
(
n
)
O(n)
O(n)个未知数组成。
1.初始化根节点
E
r
o
o
t
E_{root}
Eroot为一个未知数,当前未知数总数为1。
2.按深度递增遍历AC自动机。
3.设当前深度为
d
e
p
dep
dep,深度
≤
d
e
p
\le dep
≤dep的节点的
E
q
u
a
t
i
o
n
i
Equation_i
Equationi都已知;
对于节点
u
u
u,若是叶子结点,则将
E
u
=
0
=
E
q
u
a
t
i
o
n
u
E_u=0=Equation_u
Eu=0=Equationu扔进方程组;
否则,设
s
o
n
son
son为节点u的所有原字典树的儿子,设
s
o
n
son
son的大小为
s
z
sz
sz,将其中
s
z
−
1
sz-1
sz−1个
E
v
E_v
Ev设为新的未知数,此时
E
q
u
a
t
i
o
n
v
=
E
v
Equation_v=E_v
Equationv=Ev;并根据上述转移移项可得剩下一个儿子的
E
q
u
a
t
i
o
n
v
Equation_v
Equationv,即
E
v
=
E
u
−
1
−
∑
i
=
1
k
E
t
r
u
,
i
P
i
[
t
r
u
,
i
≠
v
]
P
v
E_v=\frac{E_u-1-\sum_{i=1}^k\frac{E_{tr_{u,i}}}{P_i}[tr_{u,i≠v}]}{P_v}
Ev=PvEu−1−∑i=1kPiEtru,i[tru,i=v]。
转移复杂度
O
(
n
2
m
k
)
O(n^2mk)
O(n2mk)。
设叶子的个数为
Z
Z
Z,等式的个数为叶子的个数
Z
Z
Z,未知数的个数为
1
1
1(根)+
Z
−
1
Z-1
Z−1(分叉的个数)
=
Z
=Z
=Z。
高斯消元复杂度
O
(
Z
3
)
O(Z^3)
O(Z3)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e4 + 10;
constexpr int mod = 1e9 + 7;
ll power(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1)res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void add(int &a, int b) { a = (a + b) % mod; }
pair<bool, vector<int>> Gauss(vector<vector<int>> a) {
int n = a.size() - 1;
for (int y = 1; y <= n; y++) {
int mx = y;
for (int x = y; x <= n; x++)if (a[x][y] > a[mx][y])mx = x;
if (!a[mx][y])return {0, {0}};
int ik = power(a[mx][y], mod - 2);
for (int i = y; i <= n + 1; i++)a[mx][i] = (ll) a[mx][i] * ik % mod;
for (int x = 1; x <= n; x++)
if (x != mx && a[x][y]) {
int k = a[x][y];
for (int i = y; i <= n + 1; i++) {
add(a[x][i], mod - (ll) a[mx][i] * k % mod);
}
}
swap(a[y], a[mx]);
}
vector<int> res(n + 1);
for (int i = 1; i <= n; i++)res[i] = a[i][n + 1];
return {1, res};
}
vector<int> operator+(vector<int> a, vector<int> b) {
int n = max(a.size(), b.size());
a.resize(n), b.resize(n);
for (int i = 0; i < n; i++)add(a[i], b[i]);
return a;
}
vector<int> operator-(vector<int> a, vector<int> b) {
int n = max(a.size(), b.size());
a.resize(n), b.resize(n);
for (int i = 0; i < n; i++)add(a[i], mod - b[i]);
return a;
}
vector<int> operator*(vector<int> a, ll b) {
b = (b % mod + mod) % mod;
for (auto &I:a)I = I * b % mod;
return a;
}
int n, m, k;
vector<int> Equation[N];
int E[N];
int p[26];
struct AC {
int tr[N][26], tot, dep[N];
int fail[N];
bool key[N];
int insert(char s[]) {
int u = 0;
for (int i = 1; s[i]; i++) {
int c = s[i] - 'a';
if (!tr[u][c])tr[u][c] = ++tot;
u = tr[u][c];
if (key[u])break;
dep[u] = i;
}
key[u] = 1;
return u;
}
void build() {
queue<int> q;
for (int i = 0; i < k; i++)if (tr[0][i])q.push(tr[0][i]);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < k; i++) {
if (tr[u][i]) {
fail[tr[u][i]] = tr[fail[u]][i], q.push(tr[u][i]);
} else
tr[u][i] = tr[fail[u]][i];
}
}
}
void calc_e() {
queue<int> q;
int tot_unknown = 0;//当前未知数个数
//Equation[u][0] 表示常数 Equation[u][1,tot_unknown]表示未知数的系数
// 初始化根节点E_root 为未知数
q.push(0);
Equation[0].assign({0, 1});
tot_unknown++;
vector<vector<int>> a(1);//等式组
while (!q.empty()) {
int u = q.front();
q.pop();
if (key[u]) {
a.push_back(Equation[u]);
continue;//one equation
}
vector<int> son;//字典树上的儿子
int p_son = -1;//第0个儿子的字符期望
for (int i = 0; i < k; i++) {
if (dep[tr[u][i]] > dep[u]) {
son.push_back(tr[u][i]);
q.push(tr[u][i]);
if (p_son == -1)p_son = p[i];
}
}
for (int i = 1; i < son.size(); i++) {//新增sz-1 个未知数
Equation[son[i]].resize(tot_unknown + 1);
Equation[son[i]].push_back(1);
tot_unknown++;
}
//求son[0]的Equation
int v = son[0];
Equation[v] = Equation[u];
add(Equation[v][0], mod - 1);
for (int i = 0; i < k; i++)
if (tr[u][i] != v) {
Equation[v] = Equation[v] - Equation[tr[u][i]] * p[i];
}
Equation[v] = Equation[v] * power(p_son, mod - 2);
}
for (auto &I:a) {
I.resize(tot_unknown + 2);
I.back() = (mod - I.front()) % mod;
}
vector<int> res = Gauss(a).second;
for (int i = 1; i <= tot; i++) {
for (int j = 1; j < Equation[i].size(); j++)
add(E[i], (ll) res[j] * Equation[i][j] % mod);
add(E[i], Equation[i][0]);//加上常数
}
}
void print_ans(char *s) {
int u = 0;
for (int i = 1; s[i]; i++) {
u = tr[u][s[i] - 'a'];
cout << (E[u] + i) % mod << "\n";
}
}
} ac;
char s[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> k;
for (int i = 0; i < k; i++)cin >> p[i], p[i] = p[i] * power(100, mod - 2) % mod;
for (int i = 0; i < n; i++) {
cin >> s + 1;
ac.insert(s);
}
ac.build();
ac.calc_e();
cin >> s + 1;
ac.print_ans(s);
}
标签:int,题解,Equation,tr,ACM,son,++,Boring,mod 来源: https://blog.csdn.net/dadaaklsjdlka/article/details/117434556