Guessing the Dice Roll
给定 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 的状态集合中,分两种情况:
为了处理第二种情况,引入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 种数字情况下连向的状态。可使用如下算法:
以状态 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
#include
#include
#include
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;
}