Codeforces Round 641 (Div. 1)
D. Slime and Biscuits
https://codeforces.com/contest/1349/problem/D
第i个人有 ai 个饼干,在所有饼干中等概率选1个饼干 并把这个饼干 等概率 给其他人
当一个人有所有饼干时,游戏停止
问 期望时间 mod 998244353
n 100‘000
$\sum a_i \in [1,300’000]$
2s
256mb
我的思路
真没想法
https://codeforces.com/contest/1349/problem/D
第i个人有 ai 个饼干,在所有饼干中等概率选1个饼干 并把这个饼干 等概率 给其他人
当一个人有所有饼干时,游戏停止
问 期望时间 mod 998244353
n 100‘000
$\sum a_i \in [1,300’000]$
2s
256mb
真没想法
n张牌,正反两面都有数字,给初始序列。
一次操作交换相邻的两张牌并翻转这两张牌。
求问能否最后所有朝上的牌构成非减序列
2<= n<=18
显然数值范围没意义,因为就算很大,离散一下也就最多36个
显然根据交换规则,把i放到j后,它的正反面是确定的
所以排列的状态数是n!
,18的阶乘就不要想枚举了
这题的边界感觉也能很难想,所以有想过的一个骗分方向是迭代加深,但是估计只能骗分答案比较小,或者n比较小的点
有人贪心100%了?? 不知道腾讯出题的人在干嘛?这样明显一堆hack点的能100%,叫那些写状压的没满的怎么想
前置知识:冒泡排序操作数 = 逆序对个数。
这题目只看操作过程中坐标的变化,实际就是在进行冒泡排序,所以我们的步数也就是 最初顺序标号变成最后顺序标号的逆序对个数
状态设计[前i个,使用的元素的state,第i个用的是下标j]
注意到state用0/1来表示是否选择,1的个数就是i,所以可以省略为[state,j]
这个状态对应的值 dp[state,j]
为 已经选出的元素的逆序对的最小值
转移方程为
dp[state][j] = min(dp[state 去掉 j][for剩余可选] + state剩余部分和j构成的逆序对)
注意到state
内部顺序不影响state剩余部分 和 j构成的逆序对的值
= min(dp[state 去掉 j][for剩余可选]) + state剩余部分和j构成的逆序对
所以 一个状态的最小,是从去掉最后一个的最小来的
(感觉这里局部最优/最小性证明有点绕,或者可以用归纳证?
注意保证题目限制非减序列别忘了
不是我写的,链接在下方,加了点注释
1 | 作者:qin_peng |
1 | #include<bits/stdc++.h> |
很早看到18 就条件反射的想2的n次方
看到交换两个也想到了逆序对和冒泡
但没想出最后的状态以及转移,果然设计状态有点难啊,我好菜哇
大概技术总结的话应该是 除了最简单的状态,还有一种是[state][末尾选择]
,这样的话能保证性质就行,如这里的末尾选择就是用来保证 非减少的
https://codeforces.com/contest/1340/problem/C
应该算bfs+dp?
状态设计 dist[第i个检查点][剩余步数j] = 到达的最小轮数 或 不可达
转移从数轴上看,只有左右关系相邻的两个点能在dist中转换,每个点最多被更新一次。
所以 O(nm)时间空间
注意的是 不能跨过g,每次转移前是j<g
,转以后的j<=g
托爷代码:https://codeforces.com/contest/1340/submission/77808028
1 |
|
还真是个大水题 放在1C
自己也在想dp,状态,还有dijs,看推有人还真的过了。
输入一个原始字符串,产生一个自动机,能识别一个给的新字符串是否是原始字符串的后缀。
给一个初始State,每次接受一个字符,进行状态转移到新的State或失败
每个State有标记是否为后缀
本文是,理清一些东西,和一些举例
对于一个给定的原始字符串,它所产生的自动机
描述 | 自己设计符号 |
---|---|
自动机的各个状态 | State |
一个字符 | char |
字符串 | String |
原始字符串的一个位置 | Pos / typedef Pos int |
自动机状态之间的转移边(接受一个字符) | next(State st,char ch) -> State |
状态的后缀链接 | link(State st) -> State |
字符串结尾位置 | endposString(String s) -> Pos数组 |
状态结尾位置 | endposState(State st) -> Pos数组 |
字符串对应的状态 | str2State(String s)-> State |
状态对应的字符串数组 | state2St(State st)-> String数组 |
给定一个字符串s,endposString(s)表示这个字符串在原始字符串中出现的结尾位置的数组
定义State:
如果两个不同的字符串s1和s2,它们的 endposString输出的数组 完全相同,则它们属于同一个State,否则它们属于不同的State
因此每个字符串对应一个State,一个State对应一个数组的字符串。(也可以把State 看作一些字符串的集合)
如果不同字符串能够结尾位置相同,那么其中一个是另一个的后缀,所以一个State中的所有字符串两两成后缀关系
因此State中最短的字符串只要在原串的任意位置出现,则State最长的也出现,所以 长度介于最短和最长之间的必定出现
所以一个State包含的是 长度递增1的,相互为后缀的一系列数组
两个State之间,如果有重复的结束位置,那么对应位置各自的两个字符串 也一定是后缀关系。注意到上面结论 一个State包含的是一个递增一的连续整数区间。所以两个State之间的关系是 max(其中一个区间) < min(另一个区间)
也有了其中一个State的所有字符串是另一个区间的字符串的后缀,所以结束位置的关系一定是包含,(长的字符串出现的位置短的一定出现,长的字符串结束位置 包含于 短的)
如果两个State 的位置是包含关系,且其中一个State1最短字符串长度 + 1 = 另一个State2最长字符串长度,那么
link(State1) = State2
, 说明State1 更短,State2更长和State1相邻
满足 link(State1)=State2 的所有state1 对应的endposState 两两交集为空,它们的并集 为 endposState(State2)
注意State会表示原始字符串子串所有能达到的状态,link以树的形式连接了所有State,从结束位置数组的角度看,每一个link分支都表示把数组拆分,数组中n个元素,所以总状态O(n)
准确点是<= 2n-1, 初始状态空字符串,包含所有n个下标, 每次拆分最多多出两个新集合, 最多n-1次拆分,所以最多1+2(n-1)=2n-1个节点
在完全完成以前一直不会标记终止状态
每个State记录三个信息
具体构建过程看wiki 和下面代码及注释,这里讲一点自己的理解
首先这是一个“归纳”性质的构建过程,所以其实考虑的是 “对于字符串构建好的自动机,在该字符串右侧末尾增加一个字符所带来的变动”
以 abcbc为例
左是next,右侧是link, 蓝色空状态, 绿色终止条件
单独看link构成的树,考虑画一个完整的每个字符串一个节点的树和link做比较,你会发现link构成的树其实是把那些没有分支的节点的部分(存在单字符没有分叉但是单独的State)路径合并成一个节点了而已
所以从这个角度,考虑一个每个字符串一个节点的link树,新增一个字符 意味着增加了一串后缀的链到原来的树上,而接到原来的树上的位置可能是叶子/根/一个已经有分叉的节点,也可能是某个节点分叉出来。(一定不会完全重合,因为一个字符串一个节点,新增的全长字符串一定不在原始的link树中。 所以上面描述的情形再考虑缩成目标的state link树。
就会和wiki中描述的操作对应了理解。1.一定会增加一个新的节点。可能找不到和-1 state连接,也可能找到已有可用的“转移”,不在操作,也可能之前会合并成一个状态的点,现在因为多了分叉需要拆成两个,同时也能理解为什么是从last开始沿着link去找是否有 char的转移
例如考虑abcb => abcbc
原来链 c-bc-abc 是一个state
现在新插入链 c-bc-cbc-bcbc-abcbc
就会变成
next 比link复杂一些,但是实际也是可以用上面的 link树来看
先考虑未进行合并的状态的影响,其实就是对所有last后缀 增加char建立边
考虑abcb=>abcbc
也就是要有边
1 | ""->"c" |
左侧出现的, 全在last的link链上
重复处理,例如”b”->”bc”其实已经有了,所以比它更短的也全有了,所以就能理解为什么wiki上的操作是last的link链上找到首个有char的边, 相当于从长到短找找最长的有的
原来link树上路径可合并的点 缩成一个state
两个有next关系的state意味着,其中一个state的所有字符串增加字符char后 的到字符串是 另一个state的子集
比如,这里之前是 state(“b”).next[“c”] = state(“bc”) == state(“c”,”bc”,”abc”)
但是 对于我们新增的state,不会有比它长的(“abc” 不期望的
但是next不像link,对结束位置并不友好,其意义是 选其中一部分下一位置是char的,在目标state中增加这些位置
但观察长度关系 也是有友好性。虽然说是集合,但是目标state中的每一个字符串 只有唯一的“上一个”字符串(也就是去掉最后一个字符的字符串),其对应的也就是唯一的state。
所以不同的相同的state中是没有重复元素的数组
next的作用就是把原来的字符串 下一个是char的部分抽出来,push到目标state的数组中
而注意到state是长度递增1的 后缀偏序数组
那么减去最后一个字符依然是长度递增1的 后缀偏序数组
state : [xxxxxxxx]c, 长度l->r
那么它的next来源
state : [xxxxxxxx], 长度l-1->len1-1
,len1->len2-1
,….,leni->r-1
每组就是一个state
所以如果wiki中描述的状态转移p->q
刚好最大长度差1,也就是 原来的划分很完美,只需要新建>=len(q)+1的
也就是说 原来的p->q
转移的划分是否和 期望的划分点一致。如果不一致就拆分
很显然上面甚至连标记结束状态都没说,而实际上标记结束状态无非是给每个state多个字段,在完全构建以后,走一遍 link
其实这个自动机构建了以后,不只是简单的后缀判断,还能做很多字符串的事情
…
见 wiki
1 | #include <bits/stdc++.h> |
https://oi-wiki.org/string/sam/
陈立杰 的 ppt
https://ac.nowcoder.com/acm/contest/5026/E
遇到了无数遍了
来讲一种常见的字符串hash吧
首先给一个字符串s,假设它只有小写英文字母
根据万物皆是数的想法
我们可以把它每一位通过 s[i]-'a'
变成数字
这样原字符串就变成了一个数列,因为全是字母
所以可以26为进制 sum((s[i]-'a')*26^i)
的形式把原来的数变成大的整数
如果两个字符串相等,那么这个大整数,相等,否则大整数不等。
通过取mod可以让它比较可以操作,和支持更长的长度
变成sum((s[i]-'a')*26^i) %p
的形式
当我们有一个字符串s0s1...sn
时
增删首尾都是 可以通过考虑计算大整数的方式去处理,预处理26的幂次模p即可
这样 长度为n的字符串的所有长度为m的连续子串的hash值,我们可以只用O(n)的时间就可完成
注意到上面的26是对于字母的情况,实际上,只要这个幂次的基
大于每一位上的最大值
即可,所以考虑单个字符的最大ascii
可以变成 sum(s[i] * b^i) %p
的形式, b 取质数重要吗迷? 然后p取尽量大的质数就行
$Hash(S) = \sum_{i=0->n-1} s[i] * b^i \mod p$