BZOJ.4820.[SDOI2017]硬币游戏(思路 高斯消元 哈希/AC自动机/KMP)
建出AC自动机,每个点向两个儿子连边,可以得到一张有向图。参照 [SDOI2012]走迷宫 可以得到一个\(Tarjan\)+高斯消元的\(O((nm)^3)\)的做法。(理论有\(60\)分啊但是第\(5.6\)个点WA了smg)
其实\(O((nm)^3)\)就是 [JSOI2009]有趣的游戏...只需建出AC自动机一遍高斯消元即可,比上面那个不知道好写到哪里去。。
\(40\)分的做法问题在于状态(变量)太多。考虑把类似的状态合并成一个。
假设现在一共有两个串\(TTH\)和\(HTT\),两个串的终止节点分别记作\(A,B\),没有经过终止节点的状态记作\(N\)。
\(N\)加上\(TTH\)一定会终止,但是在逐字符加上\(TTH\)时可能有其它情况:\(N\)的后缀与\(TT\)相连在\(B\)终止、\(N\)的后缀与\(T\)相连在\(B\)终止......总起来就是:$$NTTH=A+BH+BTH$$
其中\(BH\)就表示\(N\)在\(B\)处终止,但多出来\(H\)。
因为\(N\)中出现\(B\)的概率就是\(p(B)\),再在后面加特定的\(k\)个字符,概率就是\(p(B)\times\frac{1}{2^k}\)。
所以有:$$p(N)\times\frac18=p(A)+p(B)\times\frac12+p(B)\times\frac14\0.125p(N)=p(A)+0.75p(B)$$
扩展到多个串,记\(pre_{i,k}\)表示\(s_i\)长度为\(k\)的前缀,\(suf_{i,k}\)表示\(s_i\)长度为\(k\)的后缀,那么$$p(N+s_i)=p(s_i)+\sum_{j=1}n\sum_{k=1}m[pre_{i,k}=suf_{j,k}]\frac{1}{2{m-k}}p(s_j)=\frac{1}{2m}p(N)$$
这样我们就可以得到\(n\)个方程了,但是有\(n+1\)个变量,剩下的一个方程就是\(\sum_{i=1}^np_i=1\)。就可以高斯消元了。
复杂度\(O(n^3+n^2m)\)。
求任意两个串之间所有公共前后缀,可以哈希或KMP或者AC自动机。
对\(fail\)什么的忘的差不多了...具体写一下,也都写一遍...
哈希:没什么好说的。。前后缀哈希就把字符串看成一个\(P\)进制数好了。(向\(Kelin\ dalao\)学一波orz)
AC自动机: AC自动机上每个点向能走到它的串连一条边。然后从\(s_b\)的终止节点不断跳\(fail\),所有经过节点\(x\)上连出的边\(s_a\),都表示\(s_a\)长\(len_x\)的前缀和\(s_b\)的后缀相同。
KMP:对每个\(a\)串求出\(fail\)数组,拿\(s_a\)在\(s_b\)上匹配,最后的\(j\)指针位置就是\(s_a\)最长的等于\(s_b\)后缀的前缀,然后\(j\)不断跳\(fail[j]\)并统计答案即可。
哈希:
//2280kb 192ms
#include <cmath>
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int N=305,seed=131,LIM=(1<<30)-1;
int pw[N],pre[N][N],suf[N][N];
double pw2[N],A[N][N],Ans[N];
void Gauss(int n)
{
for(int j=0; j<n; ++j)
{
int mx=j;
for(int i=j+1; i<n; ++i) if(fabs(A[mx][j])<fabs(A[i][j])) mx=i;
if(mx!=j) for(int k=j; k<=n; ++k) std::swap(A[mx][k],A[j][k]);
for(int i=j+1; i<n; ++i)
if(A[i][j])//还是不要写>eps了= =
{
double t=A[i][j]/A[j][j];
for(int k=j; k<=n; ++k) A[i][k]-=t*A[j][k];
}
}
for(int i=n-1; ~i; --i)
{
for(int j=i+1; j<n; ++j) A[i][n]-=A[i][j]*Ans[j];
Ans[i]=A[i][n]/A[i][i];
}
}
int main()
{
static char s[N];
int n,m; scanf("%d%d",&n,&m);
pw[0]=1, pw2[0]=1;
for(int i=1; i<=m; ++i) pw[i]=pw[i-1]*seed&LIM, pw2[i]=pw2[i-1]*0.5;
for(int i=1; i<=n; ++i)
{
scanf("%s",s+1);
for(int j=1; j<=m; ++j) pre[i][j]=(pre[i][j-1]+s[j]*pw[j-1])&LIM;
for(int j=1; j<=m; ++j) suf[i][j]=(suf[i][j-1]*seed+s[m-j+1])&LIM;
}
for(int i=1; i<=n; ++i)
for(int j=1; j<=n; ++j)
for(int k=1; k<=m; ++k)//pre[i][m]==suf[i][m] -> 1
if(pre[i][k]==suf[j][k]) A[i][j]+=pw2[m-k];
for(int i=1; i<=n; ++i) A[0][i]=1, A[i][0]=-pw2[m];
A[0][n+1]=1, Gauss(n+1);
for(int i=1; i<=n; ++i) printf("%.10lf\n",Ans[i]);
return 0;
}
AC自动机:
//4460kb 192ms(常数大点...结果跑的和哈希差不多)
#include <cmath>
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int N=305,M=N*N;
double pw2[N],A[N][N],Ans[N];
struct AC_Automaton
{
int tot,len[M],son[M][2],fail[M],q[M],H[M],Enum,nxt[M],to[M],ed[N];
char s[N];
inline void AE(int u,int v)
{
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
}
void Insert(int n,int id)
{
scanf("%s",s);
int x=0;
for(int i=0,c; i<n; ++i)
{
if(!son[x][c=s[i]=='H']) len[son[x][c]=++tot]=len[x]+1;
x=son[x][c], AE(x,id);
}
ed[id]=x;
}
void Build()
{
int h=0,t=0;
if(son[0][0]) q[t++]=son[0][0];//, fail[son[0][0]]=0;
if(son[0][1]) q[t++]=son[0][1];//, fail[son[0][1]]=0;
while(h<t)
{
int x=q[h++];
for(int i=0,v; i<2; ++i)
if((v=son[x][i])) fail[v]=son[fail[x]][i], q[t++]=v;
else son[x][i]=son[fail[x]][i];
}
}
void Calc(int id,int m)
{
for(int x=ed[id]; x; x=fail[x])
for(int i=H[x]; i; i=nxt[i])
A[to[i]][id]+=pw2[m-len[x]];
}
}ac;
void Gauss(int n)
{
for(int j=0; j<n; ++j)
{
int mx=j;
for(int i=j+1; i<n; ++i) if(fabs(A[mx][j])<fabs(A[i][j])) mx=i;
if(mx!=j) for(int k=j; k<=n; ++k) std::swap(A[mx][k],A[j][k]);
for(int i=j+1; i<n; ++i)
if(A[i][j])//还是不要写>eps了= =
{
double t=A[i][j]/A[j][j];
for(int k=j; k<=n; ++k) A[i][k]-=t*A[j][k];
}
}
for(int i=n-1; ~i; --i)
{
for(int j=i+1; j<n; ++j) A[i][n]-=A[i][j]*Ans[j];
Ans[i]=A[i][n]/A[i][i];
}
}
int main()
{
int n,m; scanf("%d%d",&n,&m);
pw2[0]=1;
for(int i=1; i<=m; ++i) pw2[i]=pw2[i-1]*0.5;
for(int i=1; i<=n; ++i) ac.Insert(m,i);
ac.Build();
for(int i=1; i<=n; ++i) ac.Calc(i,m);
for(int i=1; i<=n; ++i) A[0][i]=1, A[i][0]=-pw2[m];
A[0][n+1]=1, Gauss(n+1);
for(int i=1; i<=n; ++i) printf("%.10lf\n",Ans[i]);
return 0;
}
KMP:
//1644kb 668ms(常数比哈希大复杂度还是满的)
#include <cmath>
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int N=305,M=N*N;
int fail[N];
char s[N][N];
double pw2[N],A[N][N],Ans[N];
void Gauss(int n)
{
for(int j=0; j<n; ++j)
{
int mx=j;
for(int i=j+1; i<n; ++i) if(fabs(A[mx][j])<fabs(A[i][j])) mx=i;
if(mx!=j) for(int k=j; k<=n; ++k) std::swap(A[mx][k],A[j][k]);
for(int i=j+1; i<n; ++i)
if(A[i][j])//还是不要写>eps了= =
{
double t=A[i][j]/A[j][j];
for(int k=j; k<=n; ++k) A[i][k]-=t*A[j][k];
}
}
for(int i=n-1; ~i; --i)
{
for(int j=i+1; j<n; ++j) A[i][n]-=A[i][j]*Ans[j];
Ans[i]=A[i][n]/A[i][i];
}
}
void GetFail(char *s,int m)
{
for(int i=2,j=0; i<=m; ++i)
{
while(j && s[i]!=s[j+1]) j=fail[j];
fail[i]=s[i]==s[j+1]?++j:0;
}
}
double Calc(char *s,char *p,int m)
{
int j=0;
for(int i=1; i<=m; ++i)
{
while(j && p[i]!=s[j+1]) j=fail[j];
if(p[i]==s[j+1]) ++j;
}
// if(a==b) j=fail[j];// -> 会多统计一次1
double res=0;
for(; j; j=fail[j]) res+=pw2[m-j];
return res;
}
int main()
{
int n,m; scanf("%d%d",&n,&m);
pw2[0]=1;
for(int i=1; i<=m; ++i) pw2[i]=pw2[i-1]*0.5;
for(int i=1; i<=n; ++i) scanf("%s",s[i]+1);
for(int i=1; i<=n; ++i)
{
GetFail(s[i],m);
for(int j=1; j<=n; ++j) A[i][j]=Calc(s[i],s[j],m);
}
for(int i=1; i<=n; ++i) A[0][i]=1, A[i][0]=-pw2[m];
A[0][n+1]=1, Gauss(n+1);
for(int i=1; i<=n; ++i) printf("%.10lf\n",Ans[i]);
return 0;
}
考试的时候写的很沙雕的40分:
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define eps 1e-10
typedef long long LL;
const int N=304*304;
struct AC_Automaton
{
int tot,son[N][2],ed[N],fail[N],q[N],ref[304];
char s[304];
//----- Build AC-Automaton -----
void Insert(int l,int id)
{
scanf("%s",s);
int x=0;
for(int i=0; i<l; ++i)
{
int c=s[i]=='H';
if(!son[x][c]) son[x][c]=++tot;
x=son[x][c];
}
ref[ed[x]=id]=x;
}
void Build()
{
int h=0,t=0;
if(son[0][0]) q[t++]=son[0][0];//, fail[son[0][0]]=0;
if(son[0][1]) q[t++]=son[0][1];//, fail[son[0][1]]=0;
while(h<t)
{
int x=q[h++];
for(int i=0,v; i<2; ++i)
if((v=son[x][i])) fail[v]=son[fail[x]][i], q[t++]=v;
else son[x][i]=son[fail[x]][i];
}
// for(int i=0; i<=tot; ++i) printf("i:%d son:%d %d\n",i,son[i][0],son[i][1]); puts("");
}
//----- Build Graph and Gauss -----
int cnt,out[N],dfn[N],low[N],bel[N],id[N],in[N];
double f[N],A[1005][1005];
std::vector<int> scc[N];
struct Graph
{
int Enum,H[N],nxt[N*3],to[N*3];
inline void AE(int u,int v)
{
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
}
}G,I;
inline void AE(int u,int v)
{//重边 自环...
++out[u], G.AE(u,v), I.AE(v,u);
}
void Tarjan(int x)
{
static int Index=0,top=0,sk[N];
static bool ins[N];
dfn[x]=low[x]=++Index, sk[++top]=x, ins[x]=1;
for(int i=G.H[x],v; i; i=G.nxt[i])
if(!dfn[v=G.to[i]]) Tarjan(v), low[x]=std::min(low[x],low[v]);
else if(ins[v]) low[x]=std::min(low[x],dfn[v]);
if(dfn[x]==low[x])
{
++cnt; int sz=0;
do{
int t=sk[top--]; id[t]=sz++;
scc[cnt].push_back(t), bel[t]=cnt, ins[t]=0;
}while(sk[top+1]!=x);
}
}
void Gauss(int n)
{
// printf("Gauss(%d)\n",n);
// for(int i=0; i<n; ++i,puts(""))
// for(int j=0; j<=n; ++j) printf("%.3lf ",A[i][j]);
for(int j=0; j<n; ++j)
{
int mx=j;
for(int i=j+1; i<n; ++i) if(fabs(A[i][j])>fabs(A[mx][j])) mx=i;
// if(fabs(A[mx][j])<eps) continue;
if(mx!=j) for(int k=0; k<=n; ++k) std::swap(A[mx][k],A[j][k]);
for(int i=j+1; i<n; ++i)
if(fabs(A[i][j])>eps)//fabs!!!
{
double t=A[i][j]/A[j][j];
for(int k=j; k<=n; ++k)
A[i][k]-=t*A[j][k];
}
}
for(int i=n-1; ~i; --i)
{
for(int j=i+1; j<n; ++j) A[i][n]-=A[i][j]*A[j][n];
A[i][n]/=A[i][i];
}
// for(int i=0; i<n; ++i,puts(""))
// for(int j=0; j<=n; ++j) printf("%.3lf ",A[i][j]);
}
//----- Solve -----
void Solve(int n)
{
for(int i=0; i<=tot; ++i)
if(!ed[i]) AE(i,son[i][0]), AE(i,son[i][1]);//don't AE(i,fail[i])...
for(int i=0; i<=tot; ++i)
if(!dfn[i]) Tarjan(i);
// printf("cnt:%d\n",cnt);
// for(int i=1; i<=cnt; ++i)
// {
// printf("scc[%d] = ",i);
// for(int j=0,l=scc[i].size(); j<l; ++j) printf("%d ",scc[i][j]);
// puts("");
// }// puts("");
for(int x=0; x<=tot; ++x)
for(int i=G.H[x]; i; i=G.nxt[i])
if(bel[x]!=bel[G.to[i]]) ++in[bel[G.to[i]]];
int h=0,t=0; q[t++]=bel[0], f[0]=1;
while(h<t)
{
int now=q[h++],l=scc[now].size();
// printf("\nnow:%d\n",now);
for(int j=0; j<l; ++j)
{
int x=scc[now][j];// printf("x:%d f:%.5lf\n",x,f[x]);
for(int i=0; i<=l; ++i) A[j][i]=0;
A[j][j]=1, A[j][l]=f[x];
for(int i=I.H[x],v; i; i=I.nxt[i])
if(bel[v=I.to[i]]==now) A[j][id[v]]-=0.5;//-=: 重边...
}
Gauss(l);// puts("");
for(int j=0; j<l; ++j)
{
int x=scc[now][j]; f[x]=A[j][l];
// printf("x:%d f:%.5lf\n",x,f[x]);
for(int i=G.H[x],v; i; i=G.nxt[i])
if(bel[v=G.to[i]]!=now)
{
f[v]+=0.5*f[x];
// printf("v:%d(%d) f[v]:%.5lf\n",v,bel[v],f[v]);
if(!--in[bel[v]]) q[t++]=bel[v];
}
}
}
// for(int i=0; i<=tot; ++i) printf("f[%d]=%.5lf\n",i,f[i]); puts("");
for(int i=1; i<=n; ++i) printf("%.10lf\n",f[ref[i]]);
}
}ac;
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
int n,m; scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i) ac.Insert(m,i);
ac.Build(), ac.Solve(n);
return 0;
}
BZOJ.4820.[SDOI2017]硬币游戏(思路 高斯消元 哈希/AC自动机/KMP)的更多相关文章
- 【BZOJ4820】[SDOI2017]硬币游戏(高斯消元)
[BZOJ4820][SDOI2017]硬币游戏(高斯消元) 题面 BZOJ 洛谷 题解 第一眼的感觉就是构\(AC\)自动机之后直接高斯消元算概率,这样子似乎就是\(BZOJ1444\)了.然而点数 ...
- [BZOJ 4820] [SDOI2017] 硬币游戏(高斯消元+概率论+字符串hash)
[BZOJ 4820] [SDOI2017] 硬币游戏(高斯消元+概率论+字符串hash) 题面 扔很多次硬币后,用H表示正面朝上,用T表示反面朝上,会得到一个硬币序列.比如HTT表示第一次正面朝上, ...
- BZOJ:4820: [Sdoi2017]硬币游戏&&BZOJ:1444: [Jsoi2009]有趣的游戏(高斯消元求概率)
1444: [Jsoi2009]有趣的游戏 4820: [Sdoi2017]硬币游戏 这两道题都是关于不断随机生成字符后求出现给定字符串的概率的问题. 第一题数据范围较小,将串建成AC自动机以后,以A ...
- BZOJ 4820 [Sdoi2017]硬币游戏 ——期望DP 高斯消元
做法太神了,理解不了. 自己想到的是建出AC自动机然后建出矩阵然后求逆计算,感觉可以过$40%$ 用一个状态$N$表示任意一个位置没有匹配成功的概率和. 每种匹配不成功的情况都是等价的. 然后我们强制 ...
- P3706-[SDOI2017]硬币游戏【高斯消元,字符串hash】
正题 题目链接:https://www.luogu.com.cn/problem/P3706 题目大意 给出 \(n\) 个长度为 \(m\) 的 \(H/T\) 串. 开始一个空序列,每次随机在后面 ...
- bzoj 4820: [Sdoi2017]硬币游戏【kmp+高斯消元】
有点神,按照1444的做法肯定会挂 注意到它的概率是相同的,所以可以简化状态 详见http://www.cnblogs.com/candy99/p/6701221.html https://www.c ...
- BZOJ 4820 [SDOI2017] 硬币游戏
Description 周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利.大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了.同学们觉得要加强趣味性,所以要找 ...
- 4820: [Sdoi2017]硬币游戏
4820: [Sdoi2017]硬币游戏 链接 分析: 期望dp+高斯消元. 首先可以建出AC自动机,Xi表示经过节点i的期望次数,然后高斯消元,这样点的个数太多,复杂度太大.但是AC自动机上末尾节点 ...
- 【BZOJ1444】[JSOI2009]有趣的游戏(高斯消元,AC自动机)
[BZOJ1444][JSOI2009]有趣的游戏(高斯消元,AC自动机) 题面 BZOJ 题解 先把\(AC\)自动机构建出来,最好构成\(Trie\)图.然后这样子显然是在一个有向图中有一堆概率的 ...
随机推荐
- uva11754 中国剩余定理+暴力搜索
是当y的组合数较小时,暴力枚举所有组合,然后用中国剩余定理求每种组合的解,对解进行排序即可 注意初始解可能是负数,所以如果凑不够S个,就对所有解加上M,2M.... 当y的组合数较大时,选择一个k/x ...
- Fidder 请求信息颜色的含义
颜色 含义 红色 HTTP状态错误 黄色 HTTP状态需用户认证 灰色 数据流类型CONNECT 或 响应内容是图片 紫色 响应内容是CSS文件 蓝色 响应内容是HTML 绿色 响应内容是Script ...
- 5分钟了解swagger
5分钟了解swagger https://blog.csdn.net/i6448038/article/details/77622977 随着互联网技术的发展,现在的网站架构基本都由原来的后端渲染,变 ...
- SQLServer索引及统计信息
索引除了提高性能,还能维护数据库. 索引是一种存储结构,主要以B-Tree形式存储信息. B-Tree的定义: 1.每个节点最多只有m个节点(m>=2) 2.除了根节点和叶子节点外的每个节点上最 ...
- echarts 模拟迁徙
echarts 3 的功能貌似加了不少额 官方demo:http://echarts.baidu.com/demo.html#geo-lines 地图是基于地理地图的 百度提供了一些地图:http:/ ...
- npm报错没有权限
在npm install经常会报错没有权限 这个时候需要清除一下缓存 npm cache clean --force
- [转]java位运算(1)
http://blog.csdn.net/xiaochunyong/article/details/7748713 Java提供的位运算符有:左移( << ).右移( >> ) ...
- JS如何监听动画结束
场景描述 在使用JS控制动画时一般需要在动画结束后执行回调去进行DOM的相关操作,所以需要监听动画结束进行回调.JS提供了以下事件用于监听动画的结束,简单总结学习下. CSS3动画监听事件 trans ...
- 【AtCoder】AGC013
AGC013 A - Sorted Arrays 直接分就行 #include <bits/stdc++.h> #define fi first #define se second #de ...
- google hacking
Google是一个强大的搜索引擎:而对于黑客而言,则可能是一款绝佳的黑客工具.正因为google的检索能力强大,黑客可以构造特殊的关键字,使用Google搜索互联网上的相关隐私信息.通过Google, ...