HDU-5955 Guessing the Dice Roll(AC自动机、高斯消元)

文章目录

    • 原题链接
    • 题意
    • 思路推导
    • 代码

原题链接

Guessing the Dice Roll

HDU-5955 Guessing the Dice Roll(AC自动机、高斯消元)_第1张图片 HDU-5955 Guessing the Dice Roll(AC自动机、高斯消元)_第2张图片


题意

给定 N ( 1 ≤ N ≤ 10 ) N(1 \leq N \leq 10) N(1N10) 个长度都为 L ( 1 ≤ L ≤ 10 ) L(1 \leq L \leq 10) L(1L10) 的数字序列 T i ( 1 ≤ i ≤ 10 ) T_i(1 \leq i \leq 10) Ti(1i10),数字序列仅由 { 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(1iN),当随机取数字的轮数充分多时,必定存在某一序列 T i T_i Ti S S S 的后缀匹配,因此存在如下关系:
∑ i = 1 N P i = 1 \sum_{i=1}^N P_i=1 i=1NPi=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(1jL+1) 满足:
P i , j = 1 6 ∑ 可 以 到 达 O i , j 的 状 态 P_{i,j}=\frac{1}{6}\sum可以到达O_{i,j}的状态 Pi,j=61Oi,j

HDU-5955 Guessing the Dice Roll(AC自动机、高斯消元)_第3张图片

T T T 表示数字序列, O O O 表示状态序列)
在所有能到达 O i j O_{ij} Oij 的状态集合中,分两种情况:

  1. 由状态 O i , j − 1 O_{i,j-1} Oi,j1 推出,即状态 O i O_i Oi 没有失配;
  2. 由其他序列 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(1bL)推出, 即序列 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)

HDU-5955 Guessing the Dice Roll(AC自动机、高斯消元)_第4张图片

对于这 13 13 13 个状态(包括根节点 1 1 1),需要分别推导 6 6 6 种数字情况下连向的状态。可使用如下算法:

  1. 设当前所在状态为 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 连向的状态。
  2. 如果 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 为例,其连向的状态如图所示:

HDU-5955 Guessing the Dice Roll(AC自动机、高斯消元)_第5张图片

(与状态 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=113x1,jPj+1=i=j13xi,jPj,2i13
其中, 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(1sNL+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;
}

你可能感兴趣的:(HDU-5955 Guessing the Dice Roll(AC自动机、高斯消元))