题链:

http://www.lydsy.com/JudgeOnline/problem.php?id=1444
题解.1:

概率dp,矩阵乘法,快速幂。
对所有串建立AC自动机,
那么如果在trie树的节点上转移到一个打了标记的节点,就意味着该标记对应的人取得胜利。
(由于题中明确说明串长相同,串又互不相同,所以即表明着建立AC自动机后整个trie树中只有n个打了标记的节点,同时不会存在某些节点无法转移的问题。)
然后建立trie.size×trie.size大小的转移矩阵trans,每个位置trans(i,j)表示i节点转移到j节点的概率:
初始矩阵:
if(trie.tag[i]) trans(i,i)=1;
else trans(i,trie.ch[i][c])+=p[c](枚举接下来的字符c)
此时这个矩阵的每个位置(i,j)就表明,从i走一步到j的概率。
然后将矩阵自乘很多次,就可以得到每个位置表示(i,j)从i走很多很多次后到j的概率。
那么答案就是trans(1,i).(i为打了tag标记的节点):表示从初始位置走了很多很多次后到了一个串结尾位置的概率。
由于数据小,同时矩阵转移了很多次,可以把矩阵里存的十分接近理论概率值的概率直接看成答案。
复杂度((nl)^3logP)(转移了P次矩阵,我定的是转移23336666233336666ll多次)

代码.1:

#include<bits/stdc++.h>
#define MAXN 15
using namespace std;
int N,M,L;
int pos[MAXN];
double p[MAXN];
struct Matrix{
int r,c;
double a[MAXN*MAXN][MAXN*MAXN];
void Reset(int _r,int _c){
r=_r; c=_c; memset(a,0,sizeof(a));
}
void Identity(){
for(int i=1;i<=r;i++) a[i][i]=1;
}
Matrix operator * (const Matrix &rtm) const{
Matrix now; now.Reset(r,rtm.c);
for(int i=1;i<=now.r;i++)
for(int j=1;j<=now.c;j++)
for(int k=1;k<=c;k++)
now.a[i][j]+=a[i][k]*rtm.a[k][j];
return now;
}
Matrix operator ^ (long long b) const{
Matrix now,base; base=*this;
now.Reset(r,c); now.Identity();
for(;b;base=base*base,b>>=1)
if(b&1) now=now*base;
return now;
}
};
struct Trie{
int size,p;
int ch[MAXN*MAXN][MAXN],tag[MAXN*MAXN];
Trie():size(1){}
void Insert(char *S){
static int cnt; p=1;
for(int i=0;i<L;i++){
int c=S[i]-'A';
if(!ch[p][c]) ch[p][c]=++size;
p=ch[p][c];
}
tag[p]=1; pos[++cnt]=p;
}
}T;
struct ACAM{
int fail[MAXN*MAXN];
void Build(){
static queue<int>Q;
Q.push(1); fail[1]=0;
while(!Q.empty()){
int u=Q.front(); Q.pop();
T.tag[u]|=T.tag[fail[u]];
for(int c=0;c<M;c++){
int k=fail[u];
if(!T.ch[u][c]){
T.ch[u][c]=k?T.ch[k][c]:1;
continue;
}
while(k&&!T.ch[k][c]) k=fail[k];
fail[T.ch[u][c]]=k?T.ch[k][c]:1;
Q.push(T.ch[u][c]);
}
}
}
}A;
int main(){
Matrix trans;
static char S[MAXN];
ios::sync_with_stdio(0);
cin>>N>>L>>M;
for(int i=0,a,b;i<M;i++)
cin>>a>>b,p[i]=1.0*a/b;
for(int i=1;i<=N;i++)
cin>>S,T.Insert(S);
A.Build();
trans.Reset(T.size,T.size);
for(int i=1;i<=T.size;i++){
if(T.tag[i]) trans.a[i][i]=1;
else for(int c=0;c<M;c++)
trans.a[i][T.ch[i][c]]+=p[c];
}
trans=trans^23336666233336666ll;
cout<<fixed<<setprecision(2);
for(int i=1;i<=N;i++)
cout<<trans.a[1][pos[i]]<<endl;
return 0;
}

  

题解.2:

期望dp,高斯消元
对所有串建立AC自动机,那么问题就转变为类似 BZOJ_3143_[Hnoi2013]游走 这种题目。
令dp[i]表示经过trie树上的i号节点的期望次数,pro[j][i]表示从j点转移到i点的概率。
那么就可以列出如下转移方程:
$$dp[i]=\sum_{j->i}{dp[j]*pro[j][i]}$$
特别的:
1.当j为trie树是被打了个tag标记的节点时,则不能转移给其他节点
2.当i为1号节点时,要多加一个数值1表示刚开始就期望经过了一次。
上述式子的转移存在环,需要高斯消元。

因为到达了有tag标记的节点就结束游戏不再转移,所以期望到达所有tag节点的次数为1
也就是说,每个tag节点的期望就等于到达该节点对应的人胜利的概率。

复杂度O((nl)³)

代码.2:

#include<bits/stdc++.h>
#define MAXN 15
using namespace std;
const double eps=1e-8;
int N,M,L,fail;
int id[MAXN];
double a[MAXN*MAXN][MAXN*MAXN],g[MAXN],dp[MAXN*MAXN];
double *A[MAXN*MAXN];
int dcmp(double x){
if(fabs(x)<eps) return 0;
return x>0?1:-1;
}
struct ACAM{
int size;
int ch[MAXN*MAXN][MAXN],tag[MAXN*MAXN],fail[MAXN*MAXN];
ACAM():size(1){}
void Insert(char *S){
static int p,cnt; p=1; bool fg=0;
for(int i=0;i<L;i++){
int c=S[i]-'A';
if(!ch[p][c]) ch[p][c]=++size;
p=ch[p][c];
}
tag[p]=1; id[++cnt]=p;
}
void Build(){
static queue<int>Q;
Q.push(1); fail[1]=0;
while(!Q.empty()){
int u=Q.front(); Q.pop();
tag[u]|=tag[fail[u]];
for(int c=0;c<M;c++){
int k=fail[u];
if(!ch[u][c]){
ch[u][c]=k?ch[k][c]:1;
continue;
}
while(k&&!ch[k][c]) k=fail[k];
fail[ch[u][c]]=k?ch[k][c]:1;
Q.push(ch[u][c]);
}
}
}
}DS;
void buildequation(){
for(int i=1;i<=DS.size;i++) if(!DS.tag[i])
for(int c=0;c<M;c++)
a[DS.ch[i][c]][i]+=g[c];
for(int i=1;i<=DS.size;i++) a[i][i]+=-1;
a[1][DS.size+1]+=-1;
for(int i=1;i<=DS.size;i++) A[i]=a[i];
}
void Gausselimination(int pos,int i){
if(pos==DS.size+1||i==DS.size+1) return;
for(int j=pos;j<=DS.size;j++) if(dcmp(A[j][i])!=0){
swap(A[j],A[pos]); break;
}
if(dcmp(A[pos][i])!=0)
for(int j=pos+1;j<=DS.size;j++){
double k=A[j][i]/A[pos][i];
for(int l=i;l<=DS.size+1;l++)
A[j][l]-=k*A[pos][l];
}
Gausselimination(pos+(dcmp(A[pos][i])!=0),i+1);
if(dcmp(A[pos][i])!=0){
for(int l=i+1;l<=DS.size;l++)
dp[i]+=A[pos][l]*dp[l];
dp[i]=A[pos][DS.size+1]-dp[i];
dp[i]=dp[i]/A[pos][i];
}
}
int main(){
static char S[15];
ios::sync_with_stdio(0);
cin>>N>>L>>M;
for(int i=0,P,Q;i<M;i++)
cin>>P>>Q,g[i]=1.0*P/Q;
for(int i=1;i<=N;i++)
cin>>S,DS.Insert(S);
DS.Build();
buildequation();
Gausselimination(1,1);
cout<<fixed<<setprecision(2);
for(int i=1;i<=N;i++)
cout<<fabs(dp[id[i]])<<endl;
return 0;
}

  

●BZOJ 1444 [Jsoi2009]有趣的游戏的更多相关文章

  1. BZOJ 1444:[JSOI2009]有趣的游戏

    BZOJ 1444:[JSOI2009]有趣的游戏 题目链接 首先我们建出Trie图,然后高斯消元. 我们设\(f_i\)表示经过第\(i\)个点的期望次数: \[ f_x=\sum i\cdot p ...

  2. BZOJ:4820: [Sdoi2017]硬币游戏&&BZOJ:1444: [Jsoi2009]有趣的游戏(高斯消元求概率)

    1444: [Jsoi2009]有趣的游戏 4820: [Sdoi2017]硬币游戏 这两道题都是关于不断随机生成字符后求出现给定字符串的概率的问题. 第一题数据范围较小,将串建成AC自动机以后,以A ...

  3. BZOJ 1444: [Jsoi2009]有趣的游戏 [AC自动机 高斯消元]

    1444: [Jsoi2009]有趣的游戏 题意:每种字母出现概率\(p_i\),有一些长度len的字符串,求他们出现的概率 套路DP的话,\(f[i][j]\) i个字符走到节点j的概率,建出转移矩 ...

  4. BZOJ 1444 [Jsoi2009]有趣的游戏 (AC自动机 + 概率DP + Gauss)

    1444: [Jsoi2009]有趣的游戏 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1382  Solved: 498[Submit][Statu ...

  5. bzoj 1444: [Jsoi2009]有趣的游戏【AC自动机+dp+高斯消元】

    https://blog.sengxian.com/solutions/bzoj-1444 orz 一直是我想错了,建出AC自动机之后,实际上这个定义是设f[i]为经过i节点的 * 期望次数 * ,因 ...

  6. BZOJ 1444 [JSOI2009]有趣的游戏 (AC自动机、概率与期望DP、矩阵乘法)

    诶这题洛谷居然没有??? 题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1444 题解: 我见到主要有三种做法. 一是矩阵乘法.设\(d ...

  7. BZOJ 1444 [JSOI2009]有趣的游戏 (Trie图/AC自动机+矩阵求逆)

    题目大意:给你$N$个长度相等且互不相同的模式串,现在有一个字符串生成器会不断生成字符,其中每个字符出现的概率是$p_{i}/q_{i}$,当生成器生成的字符串包含了某个模式串,则拥有该模式串的玩家胜 ...

  8. BZOJ 1444: [Jsoi2009]有趣的游戏 AC自动机+概率与期望+矩阵乘法

    这道题还比较友好~首先,构建出来 $AC$ 自动机,那么我们要求的就是从 $0$ 号点走无限次走到一个终止节点的概率. 考虑构建转移矩阵 $M,$ $M_{i,j}$ 表示节点 $i$ 转移到节点 $ ...

  9. 1444: [Jsoi2009]有趣的游戏

    1444: [Jsoi2009]有趣的游戏 链接 分析: 如果一个点回到0号点,那么会使0号点的概率增加,而0号点的概率本来是1,不能增加,所以这题用期望做. 设$x_i$表示经过i的期望次数,然后初 ...

随机推荐

  1. C语言第三次作业--嵌套循环

    一.PTA实验作业 题目1:硬币数 1. 本题PTA提交列表 2. 设计思路 步骤一:定义整型变量fen5,fen2,fen1,表示1分2分和5分,零钱数额x,总硬币数total,换法count 步骤 ...

  2. 20162330 第三周 蓝墨云班课 泛型类-Bag 练习

    目录 题目及要求 思路分析 遇到的问题和解决过程 代码实现及托管链接 感想 参考资料 题目及要求 代码运行在命令行中,路径要体现学号信息,IDEA中,伪代码要体现个人学号信息: 参见Bag的UML图, ...

  3. Build to win--来自小黄衫

    写在前面 首先非常荣幸.非常侥幸能以微弱的优势得到这次小黄衫,感谢各位老师同学的帮助,也谢谢来自<构建之法>团队的小黄衫赞助! 这次能够获得小黄衫,就像汪老师上课说的那样,其实,是一个积累 ...

  4. Vim 中文社区:期待你的加入

    我们的愿景 Vim 中文社区一直比较零散,缺少凝聚力,现有的一些群经常也是水的可以的,讨论各种无关紧要的内容,于是导致很大一部分人,将这些群丢入了群助手,渐渐地他们也淡出了 vim 中文社区. 而我理 ...

  5. Python脚本自动提取和替换代码中的中文

    # -*- coding: utf-8 -*- import os import os.path import re import sys reload(sys) sys.setdefaultenco ...

  6. JAVA_SE基础——19.数组的定义

    数组是一组相关数据的集合,数组按照使用可以分为一维数组.二维数组.多维数组 本章先讲一维数组 不同点: 不使用数组定义100个整形变量:int1,int2,int3;;;;;; 使用数组定义 int ...

  7. MongoDb进阶实践之五 MongoDB修改命令详述

    一.引言         上一篇文章我们已经详细介绍了MongoDB数据库的有关查询的内容,但是这只是所有查询命令的冰山一角.所有查询命令都写完也没有必要,我只是写了一些常用的命令,对MongoDB的 ...

  8. 剑指offer-删除链表中重复的节点

    题目描述   在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 例如,链表1->2->3->3->4->4->5 处 ...

  9. Spring Security 入门(1-3-5)Spring Security - remember me!

    Remember-Me 功能 概述 Remember-Me 是指网站能够在 Session 之间记住登录用户的身份,具体来说就是我成功认证一次之后在一定的时间内我可以不用再输入用户名和密码进行登录了, ...

  10. 【第二十一篇】手C# MVC 微信授权登录 OAuth2.0授权登录

    首先一定要熟读,最起码过一遍微信开发者文档 微信开发者文档 文档写的很清楚 授权登录四步走 在正文开始前,我得讲清楚一个事情 敲黑板,划重点:微信一共有两个 access_token 一个是7200就 ...