写在前面

此题网上存在大量题解,但本蒟蒻太菜了,看了不下10篇均未看懂,只好自己冷静分析了。本文将严格详细地论述算法(避免一切意会和玄学),因此可能会比其它题解更加理论化一些,希望能对像我一样看了其它题解还云里雾里的人有帮助。最后,为了追求极致,以下将字符串长度\(m\)加强到了10000(原题是300),并给出了一个时间复杂度到达极限的做法。

以下数学推导较多,如有错误之处欢迎批评指正!

题目描述

给定\(n\)个串,每个串仅包含字符T和F,长度均为\(m\)且互不相同。现在有一个字符串发生器,每次以等概率产生一个字符T或F,若某一时刻形成的字符串中出现了第\(i\)个串,则玩家\(i\)获胜。问每个玩家获胜的概率。

数据范围:\(n \le 300, m \le 10000\)

详细题解

一些定义

1、我们定义一个字符串\(T\)的\(q\)值为\(2^{-|T|}\),可以理解为每个字母以\(1/2\)的概率生成(但并不是严格意义的概率);

2、定义\(res(T)\)表示对于字符串\(T\),哪个玩家获胜(当\(T\)的末尾匹配\(S_i\),其它位置没有匹配任何给定串时,\(res(T)=i\);若整个串都没有匹配任何给定串,\(res(T)=0\);其他情况\(res(T)=-1\));

3、定义玩家\(i\)获胜的概率为\(P(i)\);

4、定义字符串\(T_1\)和\(T_2\)的\(link\)为所有长度\(l\)的集合,使得\(T_1\)长为\(l\)的后缀与\(T_2\)长为\(l\)的前缀相同。

方程的建立

我们的最终目标是求出\(P(i)\)。下面我们建立关于未知数\(P(i)\)的方程组,并用高斯消元求解。

引理1:\(\displaystyle P(i)=\sum\limits_{res(T) = i} {q(T)} \),且\(\displaystyle \sum\limits_{i = 1}^n {P(i)} = 1\)。

根据游戏规则可以得出。

引理2:对任意\(i\),以下式子成立:

\[\sum\limits_{res(T) = 0} {q(T{S_i})} = \sum\limits_{j = 1}^n {P(j)\sum\limits_{l \in link({S_j},{S_i})} {{2^{l - m}}} } \]

证明:任取字符串\(T_j\)满足\(res(T_j)=j\),以及\(l \in link({S_j},{S_i})\),必然对应唯一一种方式,使得将\(T_j\)结尾添加\(m-l\)个字符后,长为\(m\)的后缀为\(S_i\)。若将\(T_j\)结尾添加\(m-l\)个字符后的字符串记做\(TS_i\),则必有\(res(T) = 0\)。因此对等式右边每一个\(q(T_j)2^{l - m}\)必然对应且唯一对应等式左边一个\(q(TS_i)\)。另一方面,对于任意满足\(res(T) = 0\)的字符串\(TS_i\),取它的最短的一个\(res\)不为0的前缀(记作\(T_j,res(T_j)=j\)),那么唯一对应了等式右边的\((T_j,l)\)对;而所有长度大于\(T_j\)长度的前缀必然\(res\)值都是-1,而所有小于\(T_j\)长度的前缀res值都为0,这就意味着等式左边\(q(TS_i)\)必然对应且唯一对应右边一个\(q(T_j)2^{l - m}\)。从而对应关系是一一对应,等式左边等于右边。证毕。

引理3:对任意\(i\),\(\displaystyle \sum\limits_{res(T) = 0} {q(T{S_i})}\)的值均相同。

证明:对于每个\(i\),该值均为\(\displaystyle \sum\limits_{res(T) = 0} {q(T)\times 2^{-m}}\),故都相同。

根据引理2和引理3,我们便得到了\(n\)个关于\(P(i)\)的方程与一个附加未知数\(\displaystyle \sum\limits_{res(T) = 0} {q(T{S_i})}\)。再由引理1的第2式,便可列\(n+1\)个方程解\(n+1\)个未知数了。

\(link\)的计算

现在只剩一个问题,对任意\(i,j\)计算\(\displaystyle \sum\limits_{l \in link({S_j},{S_i})} {{2^{l - m}}}\)。

我们对所有给定的串\(S_i\)建立AC自动机。对于每个串\(S_i\)和\(S_j\),我们希望求出所有的\(link({S_i},{S_j})\)集合元素。这等价于\(S_i\)对应的AC自动机中的结束状态开始沿fail指针向上走所能到达的所有状态(这一集合设为\(F(S_i)\))中,有哪些状态在Trie树上是\(S_j\)对应结束状态的祖先。这样转化问题后,可以得到如下做法:

对于每个\(S_i\),先求出\(F(S_i)\);对于每个结点\(N \in F(S_i)\),将Trie树上\(N\)的子树中所有结点均加上一个值\(2^{l - m}\),最后对每个\(S_j\),查询Trie树对应结束状态的值即可。树上子树增加单点查询显然可以按树的dfs序用树状数组维护。

事实上,还可以进一步优化。注意到子树修改完后才询问,因此可以使用差分来修改区间,修改完后再前缀和回答询问。但是注意到Trie树的结点很多(可达\(nm\)量级),不能每次对所有结点前缀和。然而幸运的是,我们的查询仅限于Trie树结束状态的结点,因此只需要对这些结点按dfs序生成一个长度为\(n\)的区间,修改时差分,询问时前缀和即可。

时间复杂度分析

最后分析时间复杂度。首先建立AC自动机时间复杂度\(O(nm)\)。求出Trie树结束状态的结点dfs序,以及每个树上结点对应的dfs序区间时间复杂度也是\(O(nm)\)。对每个\(S_i\),求出\(F(S_i)\)然后差分修改的时间复杂度为\(O(m)\),因为\(F(S_i)\)集合大小一定不超过\(m\);最后前缀和并查询的时间复杂度为\(O(n)\)。全部求出后高斯消元的时间复杂度为\(O(n^3)\)。故总时间复杂度为\(O(n(m+n^2))\)。

总结

花了好几个小时才把理论推导理清楚,这题实在是太神了!同时对自己毒瘤的把数据范围改到\(m=10000\)表示成就感++(2333)!

AC代码

已略去高斯消元模板和AC自动机模板。

 #define LETTER 2
inline int convert(char ch){ return ch == 'T' ? : ; }
int stEnd[], dfsEnd[], cnt2;
double a[], link[][];
int l[], r[];
char s[];
struct Trie{
int num, fail, match, depth;
int next[LETTER];
}pool[];
void insert(char *s, int id)
{
int cur = ;
for (int i = ; s[i]; i++){
int &pos = trie[cur].next[convert(s[i])];
if (!pos){
pos = ++cnt;
memset(&trie[cnt], , sizeof(Trie));
trie[cnt].depth = i + ;
}
cur = pos;
}
trie[cur].num = id;
}
void dfs(int i)
{
if (trie[i].num){
dfsEnd[trie[i].num] = ++cnt2;
l[i] = r[i] = cnt2;
}
else{ l[i] = << ; r[i] = ; }
for (int j = ; j<LETTER; j++){
int id = trie[i].next[j];
if (id){
dfs(id);
l[i] = min(l[i], l[id]);
r[i] = max(r[i], r[id]);
}
}
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
init();
for (int i = ; i <= n; i++){
scanf("%s", s);
insert(s, i);
stEnd[i] = cnt;
}
dfs();
makeFail();
for (int i = ; i <= n; i++){
memset(a, , sizeof(a));
for (int st = stEnd[i]; st; st = trie[st].fail){
double t = pow(, trie[st].depth - m);
a[l[st]] += t; a[r[st] + ] -= t;
}
for (int j = ; j <= n; j++)
a[j] += a[j - ];
for (int j = ; j <= n; j++)
link[i][j] = a[dfsEnd[j]];
}
Matrix mt(n + , n + );
for (int i = ; i <= n + ; i++)
mt.a[][i] = ;
for (int i = ; i <= n; i++){
mt.a[i][] = ;
for (int j = ; j <= n; j++)
mt.a[i][j] = link[j][i];
}
mt.gauss();
for (int i = ; i <= n; i++)
printf("%.10lf\n", mt.a[i][n + ]);
}

SDOI2017 BZOJ 4820 硬币游戏 解题报告的更多相关文章

  1. 「洛谷P1080」「NOIP2012提高组」国王游戏 解题报告

    P1080 国王游戏 题目描述 恰逢 \(H\)国国庆,国王邀请\(n\)位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 \( ...

  2. BZOJ 4619 Swap Space 解题报告

    今天是因为David Lee正好讲这个题的类似题,我才做了一下. 本题是world final 2016的一道水…… 题目地址如下 http://www.lydsy.com/JudgeOnline/p ...

  3. NOIP2008 普及组T3 传球游戏 解题报告-S.B.S.

    题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同 ...

  4. 洛谷 P4705 玩游戏 解题报告

    P4705 玩游戏 题意:给长为\(n\)的\(\{a_i\}\)和长为\(m\)的\(\{b_i\}\),设 \[ f(x)=\sum_{k\ge 0}\sum_{i=1}^n\sum_{j=1}^ ...

  5. [HEOI2016/TJOI2016]游戏 解题报告

    [HEOI2016/TJOI2016]游戏 看起来就是个二分图匹配啊 最大化匹配是在最大化边数,那么一条边就代表选中一个坐标内的点 但是每一行不一定只会有一个匹配 于是把点拆开,按照'#'划分一下就好 ...

  6. BZOJ 2839: 集合计数 解题报告

    BZOJ 2839: 集合计数 Description 一个有\(N\)个元素的集合有\(2^N\)个不同子集(包含空集),现在要在这\(2^N\)个集合中取出若干集合(至少一个),使得 它们的交集的 ...

  7. 洛谷 P1129 [ZJOI2007]矩阵游戏 解题报告

    P1129 [ZJOI2007]矩阵游戏 题目描述 小\(Q\)是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏.矩阵游戏在一个\(N*N\)黑白方阵进行(如同国际象棋一般 ...

  8. 洛谷 P2059 [JLOI2013]卡牌游戏 解题报告

    P2059 [JLOI2013]卡牌游戏 题意 有\(n\)个人玩约瑟夫游戏,有\(m\)张卡,每张卡上有一个正整数,每次庄家有放回的抽一张卡,干掉从庄家起顺时针的第\(k\)个人(计算庄家),干掉的 ...

  9. 洛谷 P1450.硬币购物 解题报告

    P1450.硬币购物 题目描述 硬币购物一共有\(4\)种硬币.面值分别为\(c1,c2,c3,c4\).某人去商店买东西,去了\(tot\)次.每次带\(d_i\)枚\(c_i\)硬币,买\(s_i ...

随机推荐

  1. css中bfc和ifc

    bfc定义:块级格式化上下文,他是一个独立的渲染区域,他规定了这个内部如何布局,并且与这个区域的外部毫不相干.BFC布局规则: 内部的Box会在垂直方向,一个接一个地放置.Box垂直方向的距离由mar ...

  2. 不同场景下使用CSS隐藏元素

    使用 CSS 让元素不可见的方法很多,剪裁.定位到屏幕外.明度变化等都是可以的.虽然它们都是肉眼不可见,但背后却在多个维度上都有差别. 元素不可见,同时不占据空间.辅助设备无法访问.不渲染 使用 sc ...

  3. 《深入理解Java虚拟机》读书笔记3--垃圾回收算法

    转载:http://blog.csdn.net/tjiyu/article/details/53983064 下面先来了解Java虚拟机垃圾回收的几种常见算法:标记-清除算法.复制算法.标记-整理算法 ...

  4. 如何写jquery插件

      首页    新文章  联系  管理  订阅  自己写一个 jQuery 插件 我知道这一天终将会到来,现在,它来了. 需求 开发 SharePoint 的 CSOM 应用时,经常需要在网页上输出一 ...

  5. 东方国信 - 软件开发人员面试问卷(ver1.001.002)

    1.    通用编程知识问卷(所有编程人员必做)... 1 1.1      SQL问卷... 1 1.2      翻译... 2 2.    Java问卷(Java程序员应答,其他跳过)... 2 ...

  6. JDK内置工具之一——JMap(java memory map)

    1.介绍 打印出某个java进程(使用pid)内存内的,所有‘对象’的情况(如:产生那些对象,及其数量). 可以输出所有内存中对象的工具,甚至可以将VM 中的heap,以二进制输出成文本.使用方法 j ...

  7. Django若干新版本一些新特性

    前面那篇文章简单总结了1.9版本的新特性,其实这是我一直想做的一件事情,不过因为对于工作不是那么紧急需要,所以一直被搁浅着,今天既然做了就多做一点吧 Django1.8的新特性大概(2015年4月发布 ...

  8. 简述Action+Service +Dao 功能

    转载:http://blog.csdn.net/inter_peng/article/details/41021727 1. Action/Service/DAO简介: Action是管理业务(Ser ...

  9. php使用http_build_query,parse_url,parse_str创建与解析url详解

    1.http_build_query string http_build_query ( mixed $query_data [, string $numeric_prefix [, string $ ...

  10. lintcode 链表求和

    题目要求 你有两个用链表代表的整数,其中每个节点包含一个数字.数字存储按照在原来整数中相反的顺序,使得第一个数字位于链表的开头.写出一个函数将两个整数相加,用链表形式返回和. 样例 给出两个链表 3- ...