SDOI2017 BZOJ 4820 硬币游戏 解题报告
写在前面
此题网上存在大量题解,但本蒟蒻太菜了,看了不下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 硬币游戏 解题报告的更多相关文章
- 「洛谷P1080」「NOIP2012提高组」国王游戏 解题报告
P1080 国王游戏 题目描述 恰逢 \(H\)国国庆,国王邀请\(n\)位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 \( ...
- BZOJ 4619 Swap Space 解题报告
今天是因为David Lee正好讲这个题的类似题,我才做了一下. 本题是world final 2016的一道水…… 题目地址如下 http://www.lydsy.com/JudgeOnline/p ...
- NOIP2008 普及组T3 传球游戏 解题报告-S.B.S.
题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同 ...
- 洛谷 P4705 玩游戏 解题报告
P4705 玩游戏 题意:给长为\(n\)的\(\{a_i\}\)和长为\(m\)的\(\{b_i\}\),设 \[ f(x)=\sum_{k\ge 0}\sum_{i=1}^n\sum_{j=1}^ ...
- [HEOI2016/TJOI2016]游戏 解题报告
[HEOI2016/TJOI2016]游戏 看起来就是个二分图匹配啊 最大化匹配是在最大化边数,那么一条边就代表选中一个坐标内的点 但是每一行不一定只会有一个匹配 于是把点拆开,按照'#'划分一下就好 ...
- BZOJ 2839: 集合计数 解题报告
BZOJ 2839: 集合计数 Description 一个有\(N\)个元素的集合有\(2^N\)个不同子集(包含空集),现在要在这\(2^N\)个集合中取出若干集合(至少一个),使得 它们的交集的 ...
- 洛谷 P1129 [ZJOI2007]矩阵游戏 解题报告
P1129 [ZJOI2007]矩阵游戏 题目描述 小\(Q\)是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏.矩阵游戏在一个\(N*N\)黑白方阵进行(如同国际象棋一般 ...
- 洛谷 P2059 [JLOI2013]卡牌游戏 解题报告
P2059 [JLOI2013]卡牌游戏 题意 有\(n\)个人玩约瑟夫游戏,有\(m\)张卡,每张卡上有一个正整数,每次庄家有放回的抽一张卡,干掉从庄家起顺时针的第\(k\)个人(计算庄家),干掉的 ...
- 洛谷 P1450.硬币购物 解题报告
P1450.硬币购物 题目描述 硬币购物一共有\(4\)种硬币.面值分别为\(c1,c2,c3,c4\).某人去商店买东西,去了\(tot\)次.每次带\(d_i\)枚\(c_i\)硬币,买\(s_i ...
随机推荐
- 2018年,请不要再使用OLE生成EXCEL文件
输出EXCEL文件是ABAP开发工作中的常见需求,为了学习相关技术,我翻译过一篇文章:使用OLE2对象创建EXCEL文件,并且一度乐在其中. 最近几个月,经过与若干EXCEL打印程序的艰苦斗争,以及对 ...
- C#逻辑面试题汇总【不断更新中】
(1)产生本月的月历,参考样式: 1 2 3 4 5 6 SU MO TU WE TH FR SA 01 02 03 04 05 06 07 08 09 10 11 12 13 14 ...
- pslq常用操作
1,登录后默认自动选中My Objects 默认情况下,PLSQL Developer登录后,Brower里会选择All objects,如果你登录的用户是dba,要展开tables目录,正常情况都 ...
- javaScript(3)---语法、关键保留字及变量
学习要点: 1.语法构成 2.关键字保留字 3.变量 任何语言的核心都必然会描述这门语言最基本的工作原理.而JavaScript的语言核心就是ECMAScript 一.语法构成 区分大小写:ECMAS ...
- Delphi 项目总结
Delphi 项目总结 随着项目的失败,这些天一直在总结失败的原因,到底是为什么? 一.技术层面 1.少用指针类型,多用类. 虽然指针类型能有效的节约内 ...
- PBCS项目总结
PBCS项目已经成功地Product,终于可以缓解一下紧张的心情,最近连续四五个月紧张地工作,头都要大了.如今比较清闲,是时候总结一下整个项目了.(古人云:成功在于总结嘛) 整个项目是二个人开发的,由 ...
- 如何卸载Centos自带jdk
1.搜索安装的jdk: rpm -qa|grep jdk 结果如下: java-1.7.0-openjdk-1.7.0.45-2.4.3.3.el6.x86_64 java-1.6.0-openjdk ...
- RA layer request failed
新整的Eclipse环境出现这个问题,细化内容是不能connect,后来想起切换Eclipse底层库的事情,然后打开Eclipse的SVN设置.把SVN Client借口由JavaHL改为PureJa ...
- 全屏slider--swiper
这两年,这种滑动器技术在互联网产品介绍页设计上非常常用,最近某个项目需要这种设计,在网上找了一下,有个老外产的这种设计组件swiper已经非常成熟,原来这个东西设计初衷是pad之类的移动触摸交互设备, ...
- linux系统开机流程详解
今天,我们主要来谈谈计算机系统的启动流程 1.BIOS启动 BIOS是写入到主板上的一个韧体(韧体就是写入到硬件上的一个软件程序).开机的时候,BIOS是计算机系统会主动执行的第一个程序.BIOS主要 ...