原题链接


题意
给定
N
(
1
≤
N
≤
10
)
N(1 \leq N \leq 10)
N(1≤N≤10) 个长度都为
L
(
1
≤
L
≤
10
)
L(1 \leq L \leq 10)
L(1≤L≤10) 的数字序列
T
i
(
1
≤
i
≤
10
)
T_i(1 \leq i \leq 10)
Ti(1≤i≤10),数字序列仅由
{
1
,
2
,
3
,
4
,
5
,
6
}
\left\{1,2,3,4,5,6\right\}
{1,2,3,4,5,6} 组成。设一开始数字序列
S
S
S 为空,每轮进行如下操作:从
{
1
,
2
,
3
,
4
,
5
,
6
}
\left\{1,2,3,4,5,6\right\}
{1,2,3,4,5,6} 中等概率的选择一个数字,加在序列
S
S
S 的末尾,如果
N
N
N 条序列中存在一条序列与
S
S
S 的后缀匹配,则结束。问
N
N
N 条序列中,每条序列被匹配的概率。
思路推导
设每条序列被匹配的概率为
P
i
(
1
≤
i
≤
N
)
P_i(1 \leq i \leq N)
Pi(1≤i≤N),当随机取数字的轮数充分多时,必定存在某一序列
T
i
T_i
Ti 与
S
S
S 的后缀匹配,因此存在如下关系:
∑
i
=
1
N
P
i
=
1
\sum_{i=1}^N P_i=1
i=1∑NPi=1
即所有
N
N
N 个序列匹配的概率和为
1
1
1.
对于这
N
N
N 个序列中的其中一条
T
i
T_i
Ti,到达序列中每一个状态的概率
P
i
,
j
(
1
≤
j
≤
L
+
1
)
P_{i,j}(1 \leq j \leq L+1)
Pi,j(1≤j≤L+1) 满足:
P
i
,
j
=
1
6
∑
可
以
到
达
O
i
,
j
的
状
态
P_{i,j}=\frac{1}{6}\sum可以到达O_{i,j}的状态
Pi,j=61∑可以到达Oi,j的状态

(
T
T
T 表示数字序列,
O
O
O 表示状态序列)
在所有能到达
O
i
j
O_{ij}
Oij 的状态集合中,分两种情况:
- 由状态 O i , j − 1 O_{i,j-1} Oi,j−1 推出,即状态 O i O_i Oi 没有失配;
- 由其他序列 T a ( a ≠ i ) T_a(a \not= i) Ta(a=i) 中的某个状态 O a , b ( 1 ≤ b ≤ L ) O_{a,b}(1 \leq b \leq L) Oa,b(1≤b≤L)推出, 即序列 T a T_a Ta 失配,转为序列 T i T_i Ti的前缀串。
为了处理第二种情况,引入AC自动机。
用原题中的第三个样例作解释,建立如下 T r i e Trie Trie 树,并建立失配指针。
4 3
1 2 3
2 3 4
3 4 5
4 5 6
( N = 4 , L = 3 , T 1 = 123 , T 2 = 234 , T 3 = 345 , T 4 = 456 ) (N=4,L=3,T_1=123,T_2=234, T_3=345, T_4=456) (N=4,L=3,T1=123,T2=234,T3=345,T4=456)

对于这 13 13 13 个状态(包括根节点 1 1 1),需要分别推导 6 6 6 种数字情况下连向的状态。可使用如下算法:
- 设当前所在状态为 O i , j O_{i,j} Oi,j,遍历输入为 n u m ( n u m ∈ { 1 , 2 , 3 , 4 , 5 , 6 } ) num(num \in \left\{1,2,3,4,5,6\right\}) num(num∈{1,2,3,4,5,6}) 时,状态 O O O 连向的状态。
- 如果
O
i
,
j
O_{i,j}
Oi,j 为某序列
T
i
T_i
Ti 的终点(
j
=
L
+
1
j=L+1
j=L+1),算法结束。否则,判断
O
O
O 的下一个状态:
a . a. a. 如果状态 O i , j O_{i,j} Oi,j 到达其下一个状态 O i , j + 1 O_{i,j+1} Oi,j+1 的数字正好是 n u m num num,则状态 O i , j O_{i,j} Oi,j 对其下一个状态 O i , j + 1 O_{i,j+1} Oi,j+1 的概率 P i , j + 1 P_{i,j+1} Pi,j+1有 P i , j / 6 P_{i,j}/6 Pi,j/6 的贡献;
b . b. b. 否则,循环跳转失配指针,直到根节点状态 O r o o t O_{root} Oroot 或某个状态 O a , b O_{a,b} Oa,b 的子状态 O a , b + 1 O_{a,b+1} Oa,b+1(满足状态 O a , b O_{a,b} Oa,b 到状态 O a , b + 1 O_{a,b+1} Oa,b+1经过的数字正好是 n u m num num),状态 O i , j O_{i,j} Oi,j 对该状态的概率有 P i , j / 6 P_{i,j}/6 Pi,j/6 的贡献。
以状态 3 3 3 为例,其连向的状态如图所示:

(与状态
3
3
3 无关的状态已忽略)
对所有点进行该算法操作后,得到
13
13
13 个方程:
{
P
1
=
∑
j
=
1
13
x
1
,
j
P
j
+
1
P
i
=
∑
i
=
j
13
x
i
,
j
P
j
,
2
≤
i
≤
13
\left\{ \begin{aligned} P_1 & = \sum_{j=1}^{13} x_{1,j}P_j + 1 \\ P_i &= \sum_{i=j}^{13} x_{i,j}P_j, 2 \leq i \leq 13 \end{aligned} \right.
⎩⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎧P1Pi=j=1∑13x1,jPj+1=i=j∑13xi,jPj,2≤i≤13
其中,
x
i
,
j
x_{i,j}
xi,j 表示状态
O
j
O_j
Oj 对状态
O
i
O_i
Oi 贡献的系数。
对于这
N
L
+
1
NL+1
NL+1 个方程,把带未知量
P
s
(
1
≤
s
≤
N
L
+
1
)
P_s(1 \leq s \leq NL+1)
Ps(1≤s≤NL+1)的项与常数项分离,提取出系数矩阵
X
X
X ,常数项构成矩阵
B
B
B,得到形如如下的线性方程组:
X
P
=
B
XP=B
XP=B
样例中的
X
X
X 矩阵如下所示:
行\列 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | -4/6 | 2/6 | 2/6 | 0 | 2/6 | 2/6 | 0 | 2/6 | 1/6 | 0 | 1/6 | 1/6 | 0 |
2 | 1/6 | -5/6 | 1/6 | 0 | 1/6 | 1/6 | 0 | 1/6 | 1/6 | 0 | 1/6 | 1/6 | 0 |
3 | 0 | 1/6 | -6/6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 1/6 | -6/6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 | 1/6 | 0 | 1/6 | 0 | -5/6 | 1/6 | 0 | 1/6 | 1/6 | 0 | 1/6 | 1/6 | 0 |
6 | 0 | 0 | 0 | 0 | 1/6 | -6/6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
7 | 0 | 0 | 0 | 0 | 0 | 1/6 | -6/6 | 0 | 0 | 0 | 0 | 0 | 0 |
8 | 1/6 | 1/6 | 0 | 0 | 0 | 1/6 | 0 | -5/6 | 1/6 | 0 | 1/6 | 1/6 | 0 |
9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1/6 | -6/6 | 0 | 0 | 0 | 0 |
10 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1/6 | -6/6 | 0 | 0 | 0 |
11 | 1/6 | 1/6 | 1/6 | 0 | 1/6 | 0 | 0 | 0 | 1/6 | 0 | -5/6 | 1/6 | 0 |
12 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1/6 | -6/6 | 0 |
13 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1/6 | -6/6 |
使用高斯消元法即可解出所有
P
s
P_s
Ps 的值,其中各个终态的
P
终
态
P_{终态}
P终态 值即为题目要求的概率。
代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
int in[11][11]; // N个长度为L的序列
int child[101][7]; // trie
int fmp[101];// 失配指针
bool isends[101];// 标记trie上某个节点是否为终态
int T, n, l;
int cnt; // trie的节点数量
int root;// trie的根
double a[105][105]; // 系数矩阵
double x[105];// 解
int newnode() {
++cnt;
for (int i=1;i<=6;++i) {
child[cnt][i] = 0;
}
fmp[cnt] = 0;
isends[cnt] = false;
return cnt;
}
void init() {
cnt = 0;
root = newnode();
for (int i=0;i<105;++i) {
for (int j=0;j<105;++j) {
a[i][j] = 0;
}
x[i] = 0;
}
}
void buildtrie(int num) {
int now = root;
for (int i=0;i<l;++i) {
int c = in[num][i];
if (!child[now][c]) {
child[now][c] = newnode();
}
now = child[now][c];
}
isends[now] = true;
}
void buildfmp() {
queue<int> q;
q.push(root);
int p;
while(!q.empty()) {
int u = q.front();
q.pop();
for (int i=1;i<=6;++i) {
if (child[u][i]) {
if (u==root) {
fmp[child[u][i]] = root;
}
else {
p = fmp[u];
while(p && !child[p][i]) {
p = fmp[p];
}
if (!p) {
fmp[child[u][i]] = root;
}
else {
fmp[child[u][i]] = child[p][i];
}
}
q.push(child[u][i]);
}
}
}
}
void buildmat() {
queue<int> q;
q.push(root);
while(!q.empty()) {
int u = q.front();
q.pop();
if (isends[u]) {
continue;
}
for (int i=1;i<=6;++i) {
if (child[u][i]) {
a[child[u][i]][u] += 1;
q.push(child[u][i]);
}
else {
int p = fmp[u];
while(p && !child[p][i]) {
p = fmp[p];
}
if (!p || child[p][i]) {
a[child[p][i]][u] += 1;
}
else {
a[root][u] += 1;
}
}
}
}
for (int i=1;i<=cnt;++i) {
a[1][i] = a[0][i];
a[i][i] -= 6;
}
a[1][cnt+1] = -6;
}
void guass(int n,int m) {
int i=1,j=1,k,r,c;
double eps = 1e-12;
while(i<=m && j<=n) {
r=i;
for(k=i+1; k<=m; k++)
if(fabs(a[k][j])>fabs(a[r][j]))
r=k;
if(fabs(a[r][j])>=eps) {
for(c=1; c<=n+1; c++)
swap(a[i][c],a[r][c]);
for(k=i+1; k<=m; k++)
if(fabs(a[k][j])>=eps) {
double f=a[k][j]/a[i][j];
for(c=j; c<=n+1; c++)
a[k][c]-=f*a[i][c];
}
i++;
}
j++;
}
for(int i=n; i>=1; i--) {
for(j=i+1; j<=n; j++)
a[i][n+1]-=a[i][j]*x[j];
x[i]=a[i][n+1]/a[i][i];
}
}
int main() {
scanf("%d", &T);
while(T--) {
init();
scanf("%d %d", &n, &l);
for (int i=0;i<n;++i) {
for (int j=0;j<l;++j) {
scanf("%d", &in[i][j]);
}
buildtrie(i);
}
buildfmp();
buildmat();
guass(cnt, cnt);
for (int i=1;i<cnt;++i) {
if (isends[i]) {
printf("%.6lf ", x[i]);
}
}
printf("%.6lf\n", x[cnt]);
}
return 0;
}