Pbri

CF乱做

以前开的坑

cf上刷点题,代码用链接方式给出

10D

感觉早期的题评分都有点高,大概就是设 \(dp_{i,j}\) 为到 \(a\)\(i\) 个位置和到 \(b\)\(j\) 个位置的答案,满足 \(a_i=b_j\) ,然后设 \(f_k\)\(b\)\(k\) 这个位置的最优答案,那么有以下的\(dp\)

\(dp_{i,j}=\max_{k=1}^{j-1}(dp_{i,j},f_k+1)\)

\(f_j=dp_{i,j}\)

因为不管哪个数组都是满足越往后越优的

\(code\)http://codeforces.com/problemset/submission/10/117055277

555E

考虑如果两个点在一个边双之中,那么这两个点一定是相互可达的,只要让每个简单环都按照一个方向定向即可

把所有边双缩为一个点之后就是一棵树,下文所说的点都是缩完点后所在的点。考虑记录对于每个询问,把起始点到 \(lca\) 的路径上的边全都标记成向上,把 \(lca\) 到终点的路径上的边全都标记成向下,这个可以用树上差分来做,最后只要每个点都满足 \(up\)\(down\) 有一个值是 \(0\) 即可

\(code\)http://codeforces.com/contest/555/submission/117063785

613D

虚树模板。在学校已经琢磨出了虚树的影子,但没yy出虚树的建法,后来yy了一个特别难写的做法。回来看了眼题解学习了虚树。

特判-1的情况,就是有两个点直接连通

先把虚树建出来,定义 $res_u $为这个点的子树内可以到达上面的点的数量,然后分情况讨论

如果是重要点,那么连接的每棵子树内要么没有可以上来的点,如果有就要断一个点, \(ans+1\) ,没有就不管,因为自己这个点肯定可以上去,所以 \(res_u=1\)

如果不是重要点,考虑子树内可以上来的点的总数,如果没有直接 \(res_u=0\) ,如果只有一个那么这个点肯定可以上去也没必要在这个点断掉,上去断对于这个点来说是等价的,但是在上面断或许可以顺便帮助别的点,所以 \(res_u=1\) ,如果有超过1个点,那么这个些点在这必须断掉, \(ans+1,res_u=0\)

\(code\)https://codeforces.com/contest/613/submission/117179185

19D

先离散化,然后每个位置维护一个 \(set\) ,然后线段树上二分出右边第一个大于 \(y\)

\(code\)https://codeforces.com/contest/19/submission/123166804

1375G

ovo做的太复杂了但是思路是一样的,首先我们分析一下如果我们定下了一个点作为最终的根,那么答案应该是多少?首先我们可以把距离它为2的点全都做一下操作,然后再选距离为2的点,也就是说把子树内所有奇数层的点全都操作一下。为什么不能是偶数?因为每次操作都会使每一个位置的层数-2,所以奇数点最后还是要做一下操作,这就没有只用奇数点要优秀了。所以换根dp,考虑子树内的奇数层的点和树外面的,这个很好统计。

事实上就是黑白染色,答案就是白色和黑色中染的点的数量最少的那个颜色的答案,我做麻烦了QWQ

\(code\):https://codeforces.com/contest/1375/submission/117407189

547E

经典 \(trick\)\(AC\) 自动机的 \(fail\) 数组可形成一棵树。

我们将询问差分,然后离线,考虑先把这棵 \(fail\) 树建出来,确定每个点在 \(fail\) 树上的 \(dfs\) 序,然后按顺序更新每个字符串,查询就是查子树内点权和,具体来讲,更新指的是把字符串在 \(trie\) 树上的路径每个点的权值加一,查询可以根据 \(dfs\) 序,用树状数组进行区间求和。

\(code\)https://codeforces.com/contest/547/submission/123199735

521D

考虑只有乘法的时候就是个傻逼贪心,然后考虑如果对一个数三种操作都选的话,我们肯定是按照123的顺序,然后我们考虑把其他两种操作转换成乘法,如果没有赋值,那么加法肯定是先加大的再加小的,那么排好序之后,我们设前面所有数的贡献后的答案是 \(s\) ,那么这个数的贡献就可以变成 \(\dfrac{s+v_i}{s}\) ,然后考虑赋值,这个东西我们只保留最大的,可以将其转化为加法,然后再转化为乘法。可能会考虑我们是否会破坏123的顺序,实际上因为每个数之前的贡献顺序是固定的,我们每次操作都是考虑的和前面数共同的贡献,因为乘法顺序无所谓,所以我们这样做是对的。

\(longlong\) !!!算分数不要转化成乘法!!!会爆

\(code\)https://codeforces.com/contest/521/submission/123261301

516D

考虑我们如果把最小的那个点当根的话,那么形成的树从上到下是递增的。证明:我们只需要证明每个点的最长路径不在自己的子树内即可。首先考虑根的一个儿子,它的最长路径肯定不在子树内。如果终点在子树内部,那么根到子树外面的路径长度肯定也是小于到这个终点,然而因为在这个子树内,那么那个儿子到那个终点的距离是小于根到那个终点的距离,因为都作为最长路径,所以那个儿子的函数值肯定小于根,这与根的选择方式矛盾。然后对于一般的结点,如果它的终点在子树内部,那么运用同样的思路就可以证明。

然后你对每个点在祖先路径上二分出最靠上的符合的位置,我一开始伞兵写了个长剖。

\(code\)http://codeforces.com/problemset/submission/516/123607231

长剖版本:http://codeforces.com/problemset/submission/516/123607288

163E

神必题,写过这个 \(trick\) ,在 \(fail\) 树上做 \(dfs\) 序然后拿树状数组维护一下就好了,删除就是子树减一,增加就是子树加一,查询就是查这条路上权值之和。

\(code\)http://codeforces.com/contest/163/submission/123653784

908G

数位 \(dp\) ,考虑一个经典 \(trick\) ,一个数位上的数可以造成 \(d\times10^x\) 的贡献,那我们其实可以拆成 \(d\)\(10^x\) ,然后我们就转化成了这一位恰好是 \(d\) 的问题转化成了这一位大于等于 \(d\) 的问题,我们设 \(dp[i][j][k][l]\) 表示从高位往低位到 \(i\) ,有 \(j\) 个位置的数大于等于 \(k\) ,是否严格小于 \(l\) ,然后用 \(dp[i][j][k][l]\) 来转移,我们枚举下一位选什么,设为 \(d\) ,然后就可以贡献到 \(dp[i][j+(d\ge k)][k][l|(d<a_{i+1})]\)

\(code\)http://codeforces.com/problemset/status?my=on

1442D

首先题目中有个不降的条件,切队读完就秒出贪心策略,俺还想了半天背包。贪心策略是不存在两个只选了一部分的序列。比如设 \(a\) 选到了 \(i\)\(b\) 选到了 \(j\) ,不妨设 \(a_{i+1}\le b_{j+1}\) 那么我选 \(b_{j+1}\) 肯定要比 \(a_{i+1}\) 好,然后因为都是不降的,那么 \(b_{j+1+1}\ge b_{j+1} \ge a_{i+1}\ge a_i\ge a_{i-1}\) 所以接下来也是类似的思路。

然后剩下的就变成了一个背包问题:排除某个物品(这一列选一部分),求剩下物品形成的 \(dp\) 数组,然后我们就可以在这个 \(dp\) 数组上枚举这一列选了多少,然后更新答案。那么我们如何快速的得到排除某个物品后的 \(dp\) 数组呢?考虑线段树那样的结构,令 \([L,R]\) 表示全部选了 \([1,L-1]\)\([R+1,n]\) 中物品的 \(dp\) 数组,然后我们取 \(mid\) ,把 \([l,mid]\) 的暴力插进去,然后递归 \([mid+1,r]\) 。回溯回来之后再把这一层的 \(dp\) 数组初始化回上一层,再把右边的暴力插进去,然后递归左半边。这样为什么是对的呢?每一层每个位置的转移是 \(\Theta(n)\) 的,然后每一层总共有 \(n\) 个位置,总共有 \(\Theta(\log n))\) 层,所以总共的时间复杂度是 \(\Theta(n^2\log n)\)

\(code\)http://codeforces.com/contest/1442/submission/123931945

1039D

好暴力啊,不过根号分治一类的都挺暴力的。考虑我们能不能设计一种 \(\Theta(n)\) 的算法来对某个特定的 \(K\) 来计算答案。其实有个贪心的策略,我们用“空链”这个词表示一条未被划分进某条链的链,那么子树内会上来很多条空链,考虑最长的两条,如果这两条加自己能比 \(K\) 要大,那么我们直接合起来,那么上去的空链就是 \(0\) ,原因是如果不合,那么只有最长链能上去,上去之后最多能形成 \(1\) 的贡献,还不如在这贡献。如果合起来比 \(K\) 要小,那么就让最长空链上去。

然后我们考虑对于长度小于 \(T\)\(K\) 直接这样暴力求,对于大于的,我们考虑到他的答案一定不会超过 \(\dfrac{n}{T}\) ,也就是说会有一段的答案是相同的,而且最多 \(\dfrac{n}{T}\) 段,而且满足单调性,于是我们每次二分出右端点,那么时间复杂度就是 \(\Theta(nT+\dfrac{n^2\log n}{T})\) ,然后解一下就知道 \(T=\sqrt{n\log n}\) 的时候是最优秀的,然后他卡常,所以你搞一下 \(dfs\) 序避免递归。

\(code\)http://codeforces.com/problemset/submission/1039/124030855

757F

所有可能在最短路上的边会构成一个 \(DAG\) ,然后在 \(DAG\) 上建支配树,方法是对于 \(DAG\) 上的每个前驱求他们在支配树上的 \(lca\) ,然后由这个 \(lca\) 向这个点连边,然后对于根的每个儿子求答案就好了。

\(code\)http://codeforces.com/contest/757/submission/125122148

204E

考虑广义 \(SAM\) + 线段树合并。实际上广义 \(SAM\) 就是在 \(trie\) 上构建 \(SAM\) ,类似于 \(kmp\) 扩展成为 \(AC\) 自动机。对于一个点,考虑自己其实是 \(parents\) 树上的儿子们的后缀,所以他们出现了,自己也一定出现了。我们在这棵树上线段树合并,将儿子们出现的位置都合并起来,所以 \(l=r\) 的时候我们执行的应该是 \(or\)

说回这道题,我们的做法是考虑一个结点所代表的串等价类是否在 \(K\) 个串中出现了,然后在考虑这个串能为多少个串造成贡献。对于第一问,我们直接在 \(parents\) 树上线段树合并,因为字数内的 \(endpos\) 是自己 \(endpos\) 的子集,所以儿子们出现在哪些子串自己也一定会出现,如果次数够了就标记一下。然后考虑第二问,我们对每个右端点考虑,实际上就是每个结点到自己祖先中最近的那个标记过的点的 \(len\) ,然后对于每个结点我们都搞一个 \(vector\) 记录有多少串经过自己即可。

\(code\)https://codeforces.com/problemset/submission/204/125199823

286E

考虑一个数只能由比他小的数组合而成,所以我们从小到大一个一个检查它可不可以被比他小的数表示,如果不可以就把它加进去。那么怎么看它可不可以被表示呢?我们设 \(a_i=\sum_{i=1}^tb_i\) ,那么 \(a_i=b_1+\sum_{i=2}^tb_i\) ,首先 \(b_1\) 肯定是序列里的数,然后那个和,因为也是由序列里的数表示的,所以它肯定也是序列里的数,由此我们可以知道:一个序列里的数在解里面一定能够表示为原序列中两个数的和。所以我们直接两个数加起来即可,把出现在哪些位置的数组做 \(01\) 背包,用 \(NTT\) 加速即可。对于无解,如果一个数原来没有出现过后来出现了就说明无解。因为我们第 \(0\) 项系数为 \(0\) ,所以形成的数组的每个位置一定是由其他的表示而来,所以如果这个位置原来出现过但后来不再出现了,那么它就必须选。

\(code\)https://codeforces.com/contest/286/submission/125217268

549F

实在是太强了。首先单调栈确定每个数的范围是没跑了,接下来我们考虑,如果我们确定了一个端点,那么我们在另一个区间找前缀和对 \(k\) 取模为一个数的计数就好了,这个我们直接用 \(vector\) 以取余后的数为下标存标号,每次询问直接二分出位置就好。但是我们怎么确定那个端点呢?实际上,我们去枚举那个短的区间即可。。。至于为什么,我们考虑用 \(dp\) 求最多需要遍历多少个,那么就有 \(dp\) 式: \(f_i=\max(f_j+f_{i-j-1}+\min(j,i-j-1)\) ,显然每次都取最中间会让结果最大,然后那就是每次都分下去,就是 \(\Theta(n\log n)\) 的了。

\(code\)https://codeforces.com/contest/549/submission/125289834

一点题外话:这些题绝大多数都是我看题解学会的,希望将来能自己想出来几道吧,我真的太菜了。。。

1404D

显然这种东西要分奇偶性考虑,然后根据奇偶性划分到不同情况。手玩一下发现偶数情况下我们能构造出无解的情况,方法是构造 \((i,i+n)\) 这样的二元组,这样我们选出来的数就应该是类似于 \(\dfrac{n\times(n+1)}{2}+kn\) 这样的形式,然后 \(\dfrac{n+1}{2}\) 肯定不是个整数,所以它肯定不是 \(2n\) 的倍数。对于奇数,我们就只能考虑另一种情况,怎么求解。考虑这个时候,如果我们对模 \(n\) 的等价二元组中还是只选一个,那么肯定是 \(n\) 的倍数,所以它模 \(2n\) 要么是 \(1\) 要么是 \(n\) ,因为 \(\sum_{i=1}^{2n}i=n\times(2n+1)\equiv n\pmod {2n}\) ,所以我们只需要选它的补集即可。那么我们如何选出一组符合的来呢?考虑这样的性质:对于两个数我们只能选一个(包括 \(i\rightarrow i+n,p_{i,1}\rightarrow p_{i,2}\) ),我们想到二分图染色。那如何保证存在解呢?考虑到每个点度数是 \(2\) ,所以整个图一定是由很多环组成的,我们先把所有黑点选出来,看看符不符合,符合就输出,不符合就选白点。

\(code\)https://codeforces.com/problemset/submission/1404/125309567

79D

杜老师秒了!!考虑说我们可以把区间异或一看成差分数组上找 \(r-l=a_i\)​​ 的两个点异或一,然后先把原来的 \(01\)​​ 序列差分一下,最后可以补一个 \(1\)​​ 来搞一串后缀 \(1\)​​ 这样总共是偶数个 \(1\)​​ ,然后我们要把这个序列上的每个 \(1\)​​ 变成 \(0\)​​ ,然后假设我们可以通过某种方式求出 \(w_{i,j}\)​​ :每次通过把两个数异或 \(1\)​​ ,最终效果是只有 \(i,j\)​​ 两个位置异或了 \(1\)​​ ,所需要的最小操作数。那么我们每次肯定是去掉两个 \(1\)​​ 最优,因为去掉 \(0\)​​ 个没有意义,去掉 \(1\)​ 个我们最终还是要找一个跟他配对,​去掉多个我们可以拆成这么多,并不是说我们每个数被异或后不会再变了,而是说你再去操作最终的效果是没有变化的,而这个过程被贡献在去掉别的对里面了。然后某种方式就是根据长度连边跑 \(bfs\)​ ,走过去就相当于给这两个数异或,然后因为路径上每个点都有入度和出度所以抵消,最后只有开头和结尾只经过了一次。

\(code\)https://codeforces.com/problemset/submission/79/125458243

1404E

考虑这个东西跟最小不相交链覆盖很相似,我们考虑一开始用 \(1\times1\) 的块去覆盖,然后考虑最多能合并多少个点,然后这个题我自己的想法是通过某些方式限制这个点选择横着的还是纵向的合并,然后捣鼓了很久发现有难以修改的 \(bug\) ,于是放弃。实际上跟正解已经很相似了,我们那样做实际上是对每个点选择一个点与他合并,正解是直接找出一种合并的方案,选择这条边就意味着合并这两个点,然后横着和竖着的边不能同时选,然后选出最多的边。考虑两个不能同时选,而且要求的是最多(最小可以转换为最小割问题),所以我们考虑最大独立集。把每条边看成一个点,相邻的两个竖着的点和横着的点连边,然后跑最大独立集=边数-最小点覆盖=边数-最大匹配,可以匈牙利也可以网络流。

\(code\)​ :https://codeforces.com/contest/1404/submission/125480636

280D

一个经典的反悔的策略,策略是我们先选当前区间的最大子段,然后将整这一段取相反数,然后按照这个流程进行 \(k\) 次,然后就是答案了, 当然不要忘记求完之后需要复原回去。为什么可以这个样呢?考虑实际上就是取了很多正的区间,然后再把负数区间抠出来。挺难写的,但如果细心是可以一边写对的!

\(code\)​ :https://codeforces.com/contest/280/submission/125569324

1365G

我大受震撼,这种解法我可能一辈子也想不到。首先大家有没有做过知乎上猪和毒药的那个题!没有做过可以去搜一下,挺有意思的,这个题如果可以 \(20\)​ 次询问我们就可以类似的进行二进制分组,把第 \(i\)​ 位下标为 \(1\)​ 和 \(0\)​ 的数字求他们的或,设答案为 \(w_{i,1/0}\)​,那么我们比如查 \((101)_2\)​ 这个数的答案,我们就对每一位求与他这一位相反的答案,设从左到右的 \(i\)​ 是 \(0-2\)​,我们就可以查 \(w_{0,0}|w_{1,1}|w_{2,0}\)​ 因为只要两个数不同,那么他们的二进制位就至少有一位不同,所以不同的数一定可以在某一位取反中被或到,然后自己一定不会被或到,因为每一位都与自己相同,取反后取不到自己。这样我们对每一位都查两个值,需要 \(20\) 次。那么怎么干到 \(13\) 以内呢?我们发现,我们实际上为了更充分的储存空间,增大了每个储存空间需要的信息,增大的信息是什么呢?那就是我们需要处理两个数在这一位是 \(0-1\) 还是 \(1-0\) ,那么我们能不能浪费一下,从而让每个位置需要的信息变少?答案是可以的。如果我们可以保证对于一个数的编号 \(x\) ,它与其他任何一个数的编号 \(y\) ,都满足:有一位 \(i\) ,使得 \(x\) 的第 \(i\) 位是 \(1\) ,第 \(y\) 位是 \(0\) ,那么我们是不是就可以只用查每个 \(i\)\(w_{i,0}\) 了?为了保证这个东西,我们可以强迫它的编号的二进制位恰好有 \(k\)\(1\) ,那么任意两个数就满足这个要求了(如果没有就说明这俩完全相同),因为我们最多可以查 \(13\) 位,显然 \(k=6\) 或者 \(7\) 的时候我们能表示最多的数,发现大于 \(10^3\) ,于是就做完了。

\(code\)https://codeforces.com/contest/1365/submission/125588161

posted @ 2021-07-21 14:22  Pbri  阅读(59)  评论(0编辑  收藏  举报