●BZOJ 1444 [Jsoi2009]有趣的游戏
题链:
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]有趣的游戏的更多相关文章
- BZOJ 1444:[JSOI2009]有趣的游戏
BZOJ 1444:[JSOI2009]有趣的游戏 题目链接 首先我们建出Trie图,然后高斯消元. 我们设\(f_i\)表示经过第\(i\)个点的期望次数: \[ f_x=\sum i\cdot p ...
- BZOJ:4820: [Sdoi2017]硬币游戏&&BZOJ:1444: [Jsoi2009]有趣的游戏(高斯消元求概率)
1444: [Jsoi2009]有趣的游戏 4820: [Sdoi2017]硬币游戏 这两道题都是关于不断随机生成字符后求出现给定字符串的概率的问题. 第一题数据范围较小,将串建成AC自动机以后,以A ...
- BZOJ 1444: [Jsoi2009]有趣的游戏 [AC自动机 高斯消元]
1444: [Jsoi2009]有趣的游戏 题意:每种字母出现概率\(p_i\),有一些长度len的字符串,求他们出现的概率 套路DP的话,\(f[i][j]\) i个字符走到节点j的概率,建出转移矩 ...
- BZOJ 1444 [Jsoi2009]有趣的游戏 (AC自动机 + 概率DP + Gauss)
1444: [Jsoi2009]有趣的游戏 Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 1382 Solved: 498[Submit][Statu ...
- bzoj 1444: [Jsoi2009]有趣的游戏【AC自动机+dp+高斯消元】
https://blog.sengxian.com/solutions/bzoj-1444 orz 一直是我想错了,建出AC自动机之后,实际上这个定义是设f[i]为经过i节点的 * 期望次数 * ,因 ...
- BZOJ 1444 [JSOI2009]有趣的游戏 (AC自动机、概率与期望DP、矩阵乘法)
诶这题洛谷居然没有??? 题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1444 题解: 我见到主要有三种做法. 一是矩阵乘法.设\(d ...
- BZOJ 1444 [JSOI2009]有趣的游戏 (Trie图/AC自动机+矩阵求逆)
题目大意:给你$N$个长度相等且互不相同的模式串,现在有一个字符串生成器会不断生成字符,其中每个字符出现的概率是$p_{i}/q_{i}$,当生成器生成的字符串包含了某个模式串,则拥有该模式串的玩家胜 ...
- BZOJ 1444: [Jsoi2009]有趣的游戏 AC自动机+概率与期望+矩阵乘法
这道题还比较友好~首先,构建出来 $AC$ 自动机,那么我们要求的就是从 $0$ 号点走无限次走到一个终止节点的概率. 考虑构建转移矩阵 $M,$ $M_{i,j}$ 表示节点 $i$ 转移到节点 $ ...
- 1444: [Jsoi2009]有趣的游戏
1444: [Jsoi2009]有趣的游戏 链接 分析: 如果一个点回到0号点,那么会使0号点的概率增加,而0号点的概率本来是1,不能增加,所以这题用期望做. 设$x_i$表示经过i的期望次数,然后初 ...
随机推荐
- Beta阶段总结分析报告
1 讨论照片 2 Postmortem结果 二手交易平台项目Postmortem结果 整理:程环宇 设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有 ...
- 项目Alpha冲刺Day3
一.会议照片 二.项目进展 1.今日安排 服务器后台基本搭建完成,完成帐号权限一小部分完成并进行框架使用练手. 2.问题困难 跨专业成员不熟java的开发,有一名成员之前主要做安卓的,所以有比较多的东 ...
- 个人作业2:QQ音乐APP案例分析
APP案例分析 QQ音乐 选择理由:毕竟作为QQ音乐九年的资深老用户以及音乐爱好者 第一部分 调研 1.第一次上手的体验 我算是很早期的QQ音乐的用户,用QQ音乐七八年,除了体验各方面还不错之外 ...
- 使用 PuTTY 从 Windows 连接到 Linux 实例
启动您的实例之后,您可以连接到该实例,然后像使用您面前的计算机一样来使用它. Note 启动实例后,需要几分钟准备好实例,以便您能连接到实例.检查您的实例是否通过了状态检查 - 您可以在 Instan ...
- java8-Stream之数值流
在Stream里元素都是对象,那么,当我们操作一个数字流的时候就不得不考虑一个问题,拆箱和装箱.虽然自动拆箱不需要我们处理,但依旧有隐含的成本在里面.Java8引入了3个原始类型特化流接口来解决这个问 ...
- Python 迭代器之列表解析
 [TOC] 尽管while和for循环能够执行大多数重复性任务, 但是由于序列的迭代需求如此常见和广泛, 以至于Python提供了额外的工具以使其更简单和高效. 迭代器在Python中是以C语言的 ...
- HTML事件处理程序
事件处理程序中的代码执行时,有权访问全局作用域中任何代码. //为按钮btn_event添加了两个个事件处理程序,而且该事件会在冒泡阶段触发(最后一个参数是false). var btn_event ...
- LXC学习实践(3)快速体验第一个容器
1.搭建第一个 LXC 虚拟计算机 #yum install lxc* 2.安装软件包后要检查 Linux 发行版的内核对 LXC 的支持情况,可以使用下面命令 #lxc-checkconfig #l ...
- hadoop原理
MapReduce工作原理图文详解 前言: 前段时间我们云计算团队一起学习了hadoop相关的知识,大家都积极地做了.学了很多东西,收获颇丰.可是开学后,大家都忙各自的事情,云计算方面的动静都不太 ...
- 基于Verilog HDL的超前进位全加器设计
通常我们所使用的加法器一般是串行进位,将从输入的ci逐位进位地传递到最高位的进位输出co,由于电路是有延迟的,这样的长途旅行是需要时间的,所以为了加快加法器的运算,引入了超前进位全加器. 全加器的两个 ...