其他分享
首页 > 其他分享> > HDU5955 H - Guessing the Dice Roll (AC自动机 + 高斯消元)

HDU5955 H - Guessing the Dice Roll (AC自动机 + 高斯消元)

作者:互联网

在这里插入图片描述
题意:你有一个骰子,有六面分别为1到6,等概率的出现六个数其中之一,然后一共有n名玩家,每个玩家给出自己的一个长度为L的序列,在投掷筛子的过程中,如果有一名玩家给出的序列和目前筛出的序列的最后L位相同的话,这名玩家就赢得了游戏的胜利。问每名玩家胜利的概率?

思路:n个人(多模式串)假如我先把n个人每个人长度为L的序列当做模式串插入,然后每次的扔骰子那么就相当于站在当前节点去走向下一个节点,那么我们就可以利用AC自动机fail数组建立的过程去进行状态的转移,表示出节点之间能否互相到达的关系。

然后我们就可利用AC自动机所完成的状态转移关系,构造出一个A[i][j]数组,表示从j节点转移到i节点的概率为多少。对于某个节点i,它的概率应该是等于所有能一步到达它的节点的概率再乘1/6,那么对于整个自动机的中的每个节点我们都可以列一个方程,ex:
x 1 = a 12 ∗ x 2 + a 13 ∗ x 3 + . . . . . + a 1 n ∗ x n x_1 = a_{12}*x_2 + a_{13}*x_3 + .....+a_{1n}*x_n x1​=a12​∗x2​+a13​∗x3​+.....+a1n​∗xn​
这样我们如果让 a i i = − 1 a_{ii} = -1 aii​=−1的话就可以表示出一个A系数矩阵和X矩阵的线性方程组。

注意:到根节点的概率为1,也就是 x r o o t = 1 x_{root} = 1 xroot​=1,就相当于根节点的这个概率提供了矩阵方程组的常数矩阵。

然后就可以AC自动机建立节点之间的状态转移关系,高斯消元解矩阵方程组,然后最后我们的答案就是那些叶子节点的的概率即可。

我写的代码跑的挺慢的,等有空我再研究研究
代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN = 110;
const double eps = 1e-6;
int n;

struct Aca{
	int Next[MAXN][7],end[MAXN],fail[MAXN];
	int size;
	queue<int>q;
	
	void init(){
		for(int i = 0;i < MAXN;i ++){
			end[i] = fail[i] = 0;
			for(int c = 0;c < 7;c ++) Next[i][c] = 0;
		}
		size = 1;
	}

	void insert(int *s,int l,int k){//指针s代表要插入的串为什么 l为它的长度
		int p = 1;
		for(int i = 1;i <= l;i ++){
			int ch = s[i];
			if(!Next[p][ch]) Next[p][ch] = ++size;
			p = Next[p][ch];
		}
		end[p] = k;//end数组存的是 插入的节点是第几个插入的串
	}

	void build(){
		fail[1] = 1;
		for(int i = 1;i <= 6;i ++){//把初始与1相连的节点 放进去
			int &u = Next[1][i];
			if(!u) u = 1;
			else {
				fail[u] = 1;
				q.push(u);
			}
		}
		/*借助AC自动机失配指针 来转移相关的状态 */
		while(!q.empty()){
			int p = q.front();
			q.pop();
			for(int i = 1;i <= 6;i ++){
				int now = Next[p][i];
				if(!now){
					Next[p][i] = Next[fail[p]][i];//如果当前节点不存在 那就把它引导父节点的失配节点的对应位置
					continue;
				}
				fail[now] =  Next[fail[p]][i];
				end[now] |= end[fail[now]];//关系的转移继承
				q.push(now);
			}
		}
	}

}ac;
/**********高斯消元************/
double A[MAXN<<2][MAXN<<2],x[MAXN<<2],ans[MAXN<<2];
int equ,var;//等式的个数 变量的个数

int Gauss(){

	for(int k = 1,col = 1;k <= equ && col <= var;k ++,col ++){
		int maxr = k;
		//找出绝对值最大的一行进行消除 这样是为了 提高数值稳定性 应该可以理解为准确度
		for(int i = k + 1;i <= equ;i ++){
			if(fabs(A[i][col] > fabs(A[maxr][col])))
				maxr = i;
			if(fabs(A[maxr][col]) < eps) return 0;//无解
			if(k != maxr){//交换当前行 和 最大行
				for(int j = col;j <= var;j ++)
					swap(A[k][j],A[maxr][j]);
				swap(x[k],x[maxr]);
			}
			x[k] /= A[k][col];
			for(int j = col + 1;j <= var;j ++)
				A[k][j] /= A[k][col];
			A[k][col] = 1;
			for(int i = 1;i <= equ;i ++){
				if(i != k){
					x[i] -= x[k]*A[i][k];
					for(int j = col + 1;j <= var;j ++)
						A[i][j] -= A[k][j]*A[i][col];
					A[i][col] = 0;//已经变为零了
				}
			}
		}
	}
	return 1;
}
/***************************/

int s[27];

int main(){
	int t,l;
	scanf("%d",&t);
	while(t--){
		ac.init();
		scanf("%d%d",&n,&l);
		for(int i = 1;i <= n;i ++){
			for(int j = 1;j <= l;j ++){
				scanf("%d",&s[j]);
			}
			ac.insert(s,l,i);
		}
		// cout<<ac.size<<endl;
		ac.build();
		// cout<<"***************"<<endl;
		memset(A,0,sizeof(A));
		memset(x,0,sizeof(x));
		memset(ans,0,sizeof(ans));
		equ = ac.size,var = ac.size;
		for(int i = 1;i <= ac.size;i ++)
			A[i][i] = -1;//自己到自己变成-1 相当于等式右边
		x[1] = -1;//用来提供 常矩阵 相当于

		for(int i = 1;i <= ac.size;i ++){
			if(!ac.end[i]){
				for(int j = 1;j <= 6;j ++){
					A[ac.Next[i][j]][i] += 1.0/6;
				}
			}
		}
		// cout<<"-------"<<endl;
		// for(int i = 1;i <= equ;i ++){
		// 	for(int j = 1;j <= var;j ++){
		// 		printf(j == var?"%f\n":"%f ",A[i][j]);
		// 	}
		// }
		// cout<<"-------"<<endl;

		Gauss();

		for(int i = 1;i <= ac.size;i ++){
			if(ac.end[i])
				ans[ac.end[i]] = x[i];
		}
		for(int i = 1;i <= n;i ++){
			if(i == n) printf("%f\n",ans[i]*(-1.0));
			else printf("%f ",ans[i]);
		}
	}
	return 0;
}

标签:Guessing,AC,end,int,ac,Next,fail,节点,高斯消
来源: https://blog.csdn.net/weixin_45672411/article/details/110198946