训练记录PART2 2018.1.30~3.11
友情提醒:大部分题选自opentrains,以后有训练需要的同学慎重查看。
1.30~2.4
2.5 XVI Open Cup named after E.V. Pankratiev. GP of Ukraine
A
这题真是好有趣呀!给出一张 \(N,M \leq 10^4\) 的有向图,求有多少无序点对 \((x,y)\),满足至少存在一个 \(z\) ,使得 \(z\) 可以可到达他们两者。
数据范围看起来很诱人,但是无论怎么预处理和合并,感觉效率都是 \((NM\frac{N}{64})\) 的(bitset学傻系列)。
其实只要分析一下 \(z\) 的性质即可。对于一个合法点对,如果从 \(x\) 出发,先逆着走一些边到某个点,在顺着一些边到某个点就能到 \(y\) 。那么我们可以枚举 \(x\),设 \(f_{i,0/1}\) 表示能否从 \(x\) 走到 \(i\) ,且此后“必须要顺着走/先逆着走再顺着走”。效率显然是 \(O(NM)\)。
E
一道巧妙的签到题。要从\(N\)个人里选出\(2K\)个,分成\(K\)组,每组一个boss和一个助手,且boss的经验值不能小于助手。给出这\(N\)个人的信息,包括能胜任的职位(某种或者两种皆可)、经验值、要求的薪资。求一种合法的雇佣方案使得要付的薪资最低。\(NK \leq 10^5\)
经验值看上去比较难搞。但只要先对工人进行经验值排序(相等的要稍微处理一下),经验值就转化成了每一组“boss的编号”要小于助手。
既然有顺序,直接按顺序DP过去,记录选了几个boss和几个助手。类似于括号序列,只要每次强制boss个数要大于等于助手即可。
I
经典的后缀数组题。
后缀排序建出height后,把每次的操作“标准化一下”(找到前缀>=len的最前面的rk)再记下来。建立一棵以权值为下标的线段树,每一个节点维护一个容器,表示长度为[L,R]的所有信息。如果选择开set,每次在一个结点出暴力取出前50个归并,效率是\(O(N log^3 N)\)的(瓶颈在于,取出的效率严格来说是log^2的)。其实只要每次在线段树里判一下,如果左子树里有范围里的点就去一下左边;如果还不到k个且右子树里有范围里的点就去一下右边。这样就是\(O(N log^2 N)\)的.其实可以开大K,只是这样不能简单地用set维护了。
2.6 Petrozavodsk Summer 2015 - Warsaw U Tasks, XV Open Cup Onsite
C
感觉是很老的idea了……可愣是没做出来……
考虑开始匹配的位置是x,那么如果匹配成功,我们可以得到,\(\forall t \in [0,m) (x+t)a+b \mod n </\geq p\)。
把\(ax\)看做一个整体(未知数),以上m个式子相当于是m个区间的限制(注意某些区间可能是首和尾两段),对它们求一下交即可。
注意到\(a\)和\(n\)互质,所以\(ax\)和\(x\)是一一对应的。我们只需最后在合法区间里,扣掉那些\(x>n-m\)的即可。
D
优秀的找规律题……待填坑。
F
给出一棵树,可以加一条边并删一条边(必须依然形成树)。求可能的最长和最短直径,输出方案。
这题感觉很old很经典啊……待填坑。
J
比较old的一个思路。直接拓扑排序每次max更新即可。另一侧的点数太多,可以用线段树优化结点数量。
2.7 Petrozavodsk Summer 2015 - Petr Mitrichev Contest 13
A
这就是传说中的智商构造题吗?要求构造一个点数不超过\(\lfloor \frac{n}{2} \rfloor +1\)的非确定性01自动机,使得对于所有长度为\(N\)的01串,它能且只能识别读入给定的那个串。
画了好久的图还是不会……肯定要有一些环,但是环一旦叠加就会识别其他串了……
其实就只要把S串从左往右走过去再走回来。为了强制它不绕环,我们可以在最右的那个点多连一条子环。由奇偶性的问题与长度的限制,就只能识别给定串啦。
E
给出两个长度为N和M(\(\leq 10^6\)),数字范围在1~2的实数数组。
对于每一个i($ 1 \leq i \leq N $),求 $ \sum \limits_{j=1}^M \frac{a_i}{a_i+b_j} $。10s,相对/绝对误差 $ \leq 10^{-8} \(
一脸要FFT的样子,但是推了推发现什么都不是。
容易发现,可以把\)a_i$看做一个未知数,每次是带入同一个方程式求解。比赛时,在1~2间等距取了几个关键点,并尝试用泰勒展开去逼近关键点周围的询问。但由于余项的系数并不收敛,多展开几阶效果反而不太好。
题解也是取一些关键点。不过它更加无脑,在得到的一段段线段里直接用二次函数去拟合。回想一下辛普森,感觉二次曲线可能挺靠谱?有点玄学。
J
蛮有趣的一道题。
根据样例可以猜测,当一个序列形如“EEE……EEHH……HHH”时,所需天数最多。
那么我们可以枚举一种所需天数较小的序列。具体地,每次枚举一天的一个pattern(直接枚举有几个H,然后尽量多地塞E),然后再枚举把它循环k遍。拿这个天数k与“所有E在左边,所有H在右边”的答案对比一下,不同即找到一组解。不会证明,不过感觉挺靠谱哒。
2.8##
**首先记录一个\(O(N^2)\)的求CLCS的做法。 **
将CLCS转化成,每一个b的循环串和a做LCS并取max。
以倍长后的b串为行,a串为列画一个表格,我们可以根据本来\(N^2\)的DP倒着构建出一棵以(0,0)为结束的“最短路树”。这样,LCS可以转化到某个点的路径信息。比如(0,0)~(m,n)最短路里的经过的斜边数量即是a串和b串的LCS。
为了后来处理方便,我们规定一下“最短路树”的构建方式:优先向左,不行就左上,再不行就上。
先构好m*2行的最短路树,那么我们每次只需删除最上面那行点(同时处理好最短路树相应的变化),然后看看从\((m+i-1,n)\)到\((i-1,0)\)要经过几条斜边。
考虑删除第i行后发生的变化。我们找到一个合法的j(>=1)满足 $ parent_{i+1,j}= $ ↑ 。(如果存在必然唯一,因为$ j $之后的点必然都是←)。显然要把 $ parent_{i+1,j} $ 置为←。由于这个点的dp值少了1,我们再跳到它,考虑这个点会继续影响的点(分析一下即可知道, 不在它“子树”里的点不会受到影响了)。
假设我们现在在决策\((i,j)\)。如果 $ parent_{i+1,j}= $↑ ,那么我们将其改成←并跳下去;同理如果 $ parent_{i+1,j+1}= $ ↑,改成←并跳过去;否则上面的修改对这个点已经无影响了,我们把j向右移动一格……
容易发现,每次删除一行后,维护的效率是\(O(N+M)\)的。因此我们可以在\(O(N^2)\)的时间内解决这么一个问题。
G
直接按上述的方法去构建对应的最短路树。注意其实并没有循环,所以b串无需倍长。
当然,在实时维护当前最短路树的同时,不能每次从\((rt+m-1,n)\),\((rt+m-2,n)\)……暴力走回去。其实只需在每一层维护一个\(now_i\)表示当前层末尾走到当前rt的开头需要经过的斜边数量。容易发现,更改边的时候,也能顺带维护这个信息。
H
想让字典序最大尽量小,容易想到逐位确定。从第一位开始,每次枚举当前位置是填什么字符,再验证一下。
可惜这样做复杂度太高了,直接要乘上一个\(26N\)。
思考一下,最终答案字符串最多也只有\(N^2\)种。如果对每一个子串按字典序排好序,我们可以直接二分答案串的rank,这样效率就只要乘上一个\(log\)啦。
那么如何验证呢?首先我们必须枚举环的开头,破环成链。然后设\(f_{i,j}\)表示in这些位置是否可以划成j段。转移的话,每次我们要分析以i为开头的后缀和答案串的比较结果。如果in字典序比较小,那么可以从\(f_{i+1...n,j-1}\)转移过来;否则,只能从\(f_{i+1...k-1,j-1}\)转移过来,\(k\)是它们第一个不同的点。为了加快转移速度,可以用类似单调队列的东西维护j-1层合法的位置。
如何实现上述排序和比较的操作呢?一种比较直观的想法是,把\(n\)个循环串用极小字符接起来,跑一遍sa。比较的时候可以用经典的lcp做法。可惜上述做法都是\(N^2log(N^2)\)的,常数太大。
由此题的特殊性,可以直接设\(pos_{i,j}\)表示“\(i\)开始的循环串”和“\(j\)开始的循环串”第一处不同的位置。预处理边界后可以用bfs线性与处理好。然后我们直接拿\(N^2\)个串sort一下,即可知道每一个串的排名啦。比较的话也是拿\(pos\)怎么判一判就好了。
到现在为止,我们的复杂度是\(O(N^2log(N^2)+N*NK*log(N^2))\),依然过不了本题。但是仔细想想,当\(K=N\)时,其实破环的代价是很大的,我们甚至只需强制1是开头就好了(因为每一个点都会被作为开头)。这就启发我们用随机化的思想。
显然,如果我们每次随机\(O(\frac{N}{K})\)个开头去验证,效率是正确的。那么这样靠不靠谱呢?用数学式子算一算,每次随机后,命中某一个开头的概率是\(\frac{K}{N}\)。那么枚举了\(P\)次后,成功的概率就是\(1-(\frac{N-K}{K})^P\)。实测当\(P \geq 15\frac{N}{K}\)时,正确概率已达0.997。
这样就可以过啦。这个做法的实际写法是,随机若干个开头,取这些开头最小的二分值ans。这里有一个强力优化,每次做一个点的时候,如果\(check(ans-1)\)不合法,我们可以立即跳过它。加了这个优化后,P开得更大依旧无压力跑出。
2.14##
CF #463 E
用到一个很巧妙的思想:两边求导。
我们知道\((1+x)^n=\sum C_n^i*x^i\)。
我们想在右边造出\(i\)的若干次幂。注意到求导一次后,右侧就会出现一个i,但是x都少了1;那么我们每次对两边先求导再乘x。
得到\(n*(1+x)^{n-1}=\sum i*C_n^i*x^i\)。
这样的操作重复k次,再带入\(x=1\),右侧即是要求的式子。左侧的式子可以用dp来求系数。
CF #463 F
一眼的数据结构题……直接用平衡树维护直线,每次暴力可持久化合并即可。
不过……其实直线也可以用李超线段树直接秒掉……
这里引入李超线段树的启发式合并。在每一层,先像原来那样,合并子树(显然复杂度不变);注意到当前节点会有两个值,我们只需再暴力往下传一个值好了。注意到本来线段树合并只会访问\(O(N logN)\)个点,现在每一个点还会往下down \(O(log)\)次,所以复杂度上界是\(O(N log^2 N)\)。
2.21##
假装开几何坑,链去那里
2.22##
CF #458 F
之前做过一个不带修改的(每次只询问区间里某个串出现了几次),sam套一个right的线段树合并即可;数据范围可以更大。
套了修改后就不那么好做了。一个很赖皮的思路是用bitset。对于一个询问串的每个位置上的字符,它在原串里会有一些限制;把这些限制交起来,在\([l,r]\)之间的合法个数即为答案。这样做是无脑的\(sum_Q*\frac{N}{64}\)。
因为是单点修改,数据范围又小,可以尝试去乱搞。
最好想的是:如果询问串长度大于一定的阈值K。那么我们可以暴力和原串kmp比较一下,简单好写,效率是\(O(\frac{sum_Q}{K}*N)\)。因为CF比较快,这个K甚至可以开得很小。
对于长度小的询问,很自然的想法是拿哈希判一判。注意到每次修改一个点的时候,对于长度为\(len\)的询问,我们要花费\(O(len)\)的时间维护它每一个位置的哈希值。不过询问时还要搞一个数据结构,来控制合法区间。看上去速度很爆炸,注意到不同长度的询问只有\(O(\sqrt {sum_Q})\)种,而且阈值K还可以更小。实际上大概取\(K=20\)就能过了。对于相同长度的小串,把相同哈希的位置排在一起,共同放在一个树状数组里。询问时定位某种哈希值的真实位置,然后在树状数组里询问。效率大概是\(O(\frac{sum_Q}{K}*N+QK^2log)\)。
其实还可以改进。询问子串个数的时候,很容易想到用后缀结构。可以再对此套上一个分块:将字符串按N分成K段,一共维护\(O(\frac{N}{K})\)个后缀结构。每次修改的时候,直接暴力重构\(O(1)\)个块。
不过如果处理得不好,很容易在询问的时候加一个log。
我们可以用后缀自动机来优美地解决这个问题。具体的,我们定义一段里字符串str出现的次数为:s[l..r]=str且r在这一段里r的个数。询问小串的时候,假设询问指针ql,qr分别指向bl,br。那么在bl+2~br-1这些块里我们都暴力询问。之前先对每段sam预处理出每一个节点有多少个right(根据定义,建的时候是上个block和这个block放在一块建,但是只有这个块的block的right集合会产生贡献),这样块间询问可以做到\(O(len*\frac{N}{K})\)。在边上的那些块就直接再跑kmp即可,效率是\(O(QK)\)。取\(K=\sqrt N\)的时候,因为\(Q=N=sum_Q\),总效率是\(O(N \sqrt N)\)。
2.28 Petrozavodsk Winter 2017 - Xiaoxu Guo Contest 5##
C
有一张\(N(\leq 50)\)个点的无向图,保证每一条边两侧点的标号差\(\leq 13\)。问有多少种点集选择方案,使得选出的点集是连通的,输出答案的奇偶性。
最直接的思路是每次记录13个点的连通性做一个连通性dp。但是13个点的最小表示有\(2+kw\)中,妥妥得TLE了。
所以我们要尽量避开和连通性有关的DP。
对于一种点集的选取方案S,假设它有K个连通块,我们对它们进行黑白染色(同一个连通块颜色相同)。容易发现,S贡献的方案数是\(2^k\)。在原题中,\(k=1\)对答案贡献1。如果我们求出了所有S的染色方案数,那么我们只需对其/2再%2即可得出答案了。
那么如何求染色方案数呢?其实相当于就是对于所有点3种颜色(点集外的点染灰色),而且黑色和白色的点之间不能有边相连。容易发现,这里统计的方案和答案的染色方案是一一对应的。对这个染色问题进行\(O(3^n*n)\)DP即可。
D
有面值分别为 $1 $ ~ $ n(\leq 15) $ 的硬币,每种有 $ a_i(\leq 10^9) $ 个。求它们能拼成多少种权值。
不妨设m=lcm(1..n)。我们会发现,如果一种权值\(x(x \geq nm)\)能被拼成,必然存在一种面值 i ,它的数量能达到\(\frac{m}{i}\),\(x-m\)也必然能被拼成。(可以考虑反证,如果每种面值拼成总值都\(<m\),那么总和必然\(<nm\))。
同理,反向考虑。我们先取上所有的硬币,权值和为sum。如果有一种权值x(\(x \leq sum-nm\))能被拼成,那么x+m也必然能被拼成。
由以上,如果 \(nm-i\) (\(1 \leq i \leq m\)) 可以取到,那么 $ x=(nm-i)+km $ 可以取到,其中必须保证 \(x \leq sum-nm\) 。头尾两段可以用多重背包暴力计算。
F
有一张\(N=70\)的0/1有向图,每一秒后,会从当前点\(x\)等概率随机转移到一个它连出去的点。总时间轴为\(T \leq 2*10^6\),有\(M \leq 10^4\)个事件:时刻\(t_i\)有一个人在第\(x_i\)个点出现。对于时间轴上的每一个时刻,都要求此时有多少人在第n个点。答案在模域下表示。
看上去,对于每一个时刻都需要维护当前每一个点上期望人数,这是一个\(1*n\)的向量\(V\)。转移可以看做\(V\)和\(n*n\)的转移矩阵相乘,效率是\(O(N^2)\)的。
怎样加速这么个过程呢?我们发现,每次在一个时刻求答案的时候,其实只是\(V\)和转移矩阵的一个列向量相乘,这样复杂度是正确的;只是我们无法每次都快速维护出\(V\)。
这启发我们用分块来解决。设一个常量\(ly\),我们先去预处理出转移矩阵\(1\)~\(ly\)次幂的矩阵。这样,如果两个关键点之间距离\(\leq ly\),对于其中的每一个时间,我们都可以\(O(N)\)快速求得答案。
但是如果距离很大呢?对于之前的一段\(ly\),我们如法炮制算一遍;然后我们再通过调取预处理的数组,求出当前点开始过时间\(ly\)后的\(V\)。
通过这样的手段,复杂度变成了\(O(ly*n^3+n*T+n^2*(\frac{T}{ly}+M))\)
3.1 Petrozavodsk Winter 2017 - Jagiellonian U Contest##
K
YY了一个写起来细节爆炸的做法……
一个很自然的思路是,枚举询问串的每一个位置,再枚举替换的字母,然后求一遍答案。
注意到每次中间某个位置字母换过,如果是AC自动机或者后缀自动机,是不支持替换中间某个点然后询问整个串所在的节点位置的。
那么如何快速求答案呢?采用经典的sa的height数组。
首先把原串和询问串放在一起(用极小值连接)做一个sa。
对于某个询问串s,我们枚举把第i位改掉,即\(s=s1+new_i+s2\)。
我们先在sa中,跑出一个区间[L1,R1],使得L1~R1里前i-1位都和s1相同。这个可以在rmq数组二分出来。
现在有一个新字符\(new_i\),我们再在区间[L1,R1]里二分出一个区间[L2,R2],使得区间里的所有后缀前i位都和s相同。
最后还有一段s2。也就是说,现在我们要在[L2,R2]里再次二分出一个区间[L3,R3],使得区间里所有后缀第i+1位开始的串都是s2。那么答案就是L3~R3里的合法后缀个数(这里面可能掺杂着询问串,当然不产生贡献)。
不过细节比较复杂,我以求[L3,R3]为例。
我先通过二分求出L3,要求L3尽量小,且rk=L3的后缀第i+1位开始的串字典序大于等于s2。(比较字典序的时候,我们可以通过比较s2和sa[L3]+i+1这两个串的lcp来判断)然后再在rmq数组上二分,拓展出一个尽量大的R3,使得[L3,R3]里的后缀第i+1位开始的串都是s2的前缀。
每次二分都是独立的;加上枚举修改的新字符,效率是\(O(NlogN+sumQ*26*log(N+sumQ))\)。
3.4 Petrozavodsk Winter-2016. SPb*U Contest.
L
给出\(N\)个不同的数\((\leq M)\),选取三个拼三角形,问能拼出的最小面积是多少。\(N,M \leq 10^5\)
设最后选取的是\(a<b<c\)。不妨枚举\(d=c-b\)。容易证明,在这种情况下,肯定选取>d的最小的a。
当确定了a和d后,合法的\((b,c)\)选取方案是平面里的双曲线。容易发现,b(c)越小面积越小。所以就是找一个最小>a的b,使得数b,b+d都存在。
以上这个操作可以用bitset实现,and一下后要支持快速找到>pos的最前面的1(这要手写)。
因为时间卡得很紧,数据组数也很多(只保证∑),所以bitset的位要按实际数据开;同时还要加点小优化,比如从小到大枚举d,如果\((a,a+1,a+d+1)\)的面积都大于了当前答案,就直接skip了这次操作。
3.10 2017 ICPC Asia Tsukuba
D
给出平面里的\(N(\leq 10^5)\)个点。最多删掉两个点,使得剩余点围成的凸包的周长最小。
分类讨论好题。不妨先思考删掉一个点怎么做。
显然肯定删在凸包上的某个点。我们枚举它,暴力的做法是,找到它所在的凸壳的左边和右边那两个点,然后把横坐标在它们之间的点重新跑一遍构一个更小的凸壳。注意到,一段区间里的点只会被遍历到两次,所以复杂度是对的。
删除两个点会更加麻烦。
①只有一个点删在凸包上。我们先删除那个点,再重构这个凸包。那么第二个删的点,必然在是新凸壳的某个点,继续暴力删暴力构即可。
②两个点都在凸包上且相邻。这样我们也是对横坐标在左右两侧点之间的点重新构一个凸壳。此时一段区间会被遍历到三次。
③两个点都在凸包上且不相邻。对于凸包上的点x,我们设\(f(x)\)表示删掉x的点后周长能减少多少。然后打擂找到\(f\)最大的四个点#1,#2,#3,#4。显然,如果1不与#2相邻,答案就是#1+#2;如果#1不与#3相邻,答案就是#1+#3;否则比较一下#1+#4和#2+#3即可。
E
有一面\(N(\leq 5*10^5)\)个格子的墙,初始已全部涂好了黑色或者白色。同时给定最终态每个格子要求的颜色。你有一把刷子,每次可以把连续的最多\(K\)个格子全部涂成黑色或者白色。问最少刷几次可以从初始态走到最终态。
我们可以get到几个性质:
①一个格子最多只会被刷到两次。
②最左边的格子最多只会被刷一次。
③把所有刷的区间都提出来,这些线段只会包含或者相离(不会非包含的相交)。
因此,我们可以设\(f_i\)表示粉刷\(i\)$n$这一段到目标态最少要几次。如果$a_i=b_i$,显然$f_i=f_{i+1}+1$。否则,假设我们枚举刷的那一段是$i$\(j\)(注意\(j-i+1 \leq k\)。由性质①和③,这一段之间不符合要求的段,必然是一段一段独立地刷掉。这个贡献可以用前缀和表示。最后发现这个dp式可以用单调队列或者线段树优化。
3.11 Makoto Soejima (rng_58) Сontest 4
A
这就是一道sb暴力题……
最终答案只有1k个左右。因此我们可以预先跑出一个表。
实时维护出只加入\(1\)~\(k\)这些质数时的答案。尝试加入\(k+1\)时,直接在目前的每一个数后面暴力乘上若干个\(prime_k\)(注意允许至多有一个数因数大于它,所以当前用的质数个数\(\leq\)上一个质数个数这个剪枝是错的)。获得了新的数列后,sort一遍,然后把现在已经不合法的数字踢掉,重复迭代。直到一次迭代后数列不发生变化,则已经到了最终态。
D
设\(f_{i,j}\)表示\(1\)$i$,$1$\(j\)这两个数列的答案。如果\(a_i \neq b_i\),那么考虑最后一个数字是啥,显然有\(f_{i,j}=f_{i-1,j}+f_{i,j-1}\)。但如果\(a_i=b_j\)时,我们还要减去这两个DP式里重复的数列。我们枚举\(k\),现在要减掉某种重复数列,它存在某一步状态是选到\((i-k,j-k)\),且这个k是极小的。那么显然要减掉的答案是”\(f[i-k][j-k]\)*“(i+k+1,j-k+1)里选不同的数列的方案数“。注意到必须满足\(a_{i-k+1..i}=b_{j-k+1..j}\),且打表发现方案数就是\(Catalan_k\)。
F
转化一下题意:给出少量限制,形如 $ \lfloor \frac{a_i}{x} \rfloor \oplus \lfloor \frac{b_i}{x} \rfloor $(opt是<或=)。求由多少个合法的x满足题意,范围都是\(10^9\)。
我们可以对x进行分块。
设定一个阈值K,对于所有<=K的x我们可以暴力枚举验证。
对于>K的x,我们枚举每一组限制的 $ d=\lfloor \frac{b_i}{x} \rfloor $ ,然后可以计算出对应的一段合法的x区间。每组限制是一些区间的并,最后我们只需把所有限制交起来,算一下由几个x即可。
H
预处理\(g_i\)表示从某个点出发,走i步后第一次回到自己的方案数(这个可以用过\(N^2\)的充斥求出)。
求答案也要用容斥。我们用\(4^n*(n+1)\)减去所有重复经过的格子。每次对于一个重复经过的格子,我们把它计算到“上一次经过它且第一次回到它”的方案里。第一次回到它时可以经过别的点,反正我只统计重复经过这个格子的贡献。考虑在所有方案里,这里不合法情况的总和。我们枚举第一次回到它时的步长i。那么在整个n序列里,这一段i出现的位置个数是\(n-i+1\),这一段的贡献是\(g_i\)。然后总方案还要乘上\(4^n\)。