做题笔记乱写/口胡
碰到有难度/价值的题会随手记下来,有思路准备稍后再写的也会记下思路。
P7710 [Ynoi2077] stdmxeypz
根号分治。
-
\(x>\sqrt{n}\):暴力往下跳,最多跳 \(O(\sqrt{n})\) 层,每层在
bfs
序上是连续的,找到这段bfs
序区间后区修单查即可(\(O(1)-O(\sqrt{n})\),差分),具体地,每层对dfs
序排序,二分找到 \(x\) 的子树这一层中最左和最右的结点,这两个点构成的bfs
序区间即为所求。 -
\(x\le \sqrt{n}\):离线后对于每个 \(x\),对所有结点深度 \(\bmod x\) 的结果为第一关键字、
dfs
序为第二关键字排序(基数排序可做到 \(O(n)\)),那么每次修改在一个区间内,区修单查即可(\(O(\sqrt{n})-O(1)\))。
\(x>\sqrt{n}\) 直接修改即可,\(x\le \sqrt{n}\) 则要 \(O(m)\) 对后面的询问加上贡献(每次排序的结果不一样)。总共 \(O((n+m)\sqrt{n}\log n)\),瓶颈在于 \(x>\sqrt{n}\) 时暴力往下跳并二分。看了下题解发现要在此处扔掉这个 \(\log\) 需要用长链剖分什么的做到 \(O(n\log n)-O(1)\),不想学了。
正在卡常。如果卡不过就乖乖去学长链剖分罢。
P3731 [HAOI2017]新型城市化
从 \(n\) 个点的无向完全图中删去 \(m\) 条边,得到的图恰好有两个极大完全子图。在被删去的 \(m\) 条边中,求出所有的边,满足:加上这条边后,极大完全子图的大小得以增加。
正♂男♂则♂反♂,直接在原图的补图上展开讨论。因为恰好有两个极大完全子图,完全图在补图中内部不会有边相连,所以这就是一张二分图。原图中加上一条边,相当于在补图中删掉一条边;原图中极大完全子图的大小得以增加,即补图中最大独立集的大小得以增加。
问题转换成:给定 \(n\) 点 \(m\) 边的无向图,求出所有的边,满足:删掉这条边后,最大独立集的大小得以增加。
众所周知,\(\text{最大独立集}=\text{结点数}-\text{最大匹配}\),所以我们只需求出所有的边,满足:删掉这条边后,最大匹配减少,即这条边必定在最大匹配中。跑 dinic
,一条边必定在最大匹配中的充要条件是:这条边的反向边流量非零且两个点在建出的网络里不在同一个强连通分量中。
感性理解,反向边流量非零表示该边在至少一个最大匹配中;两点不在同一个强连通分量中中告诉我们这条边找不到一个替代品,否则沿着这条边所在的边双形成的环增广一次,这条边的反向边流量即变成零,该边可以不在最大匹配中。
P3899 [湖南集训]更为厉害
求满足如下要求的 \((b,c)(b\ne p,c\ne p)\) 个数:
-
\(p\) 和 \(b\) 都为 \(c\) 的祖先;
-
\(dis(p,b)\le k\)。
\(sub()\) 与 \(siz\) 均不包含自身
主席树,按 dfn
依次插入 \(dep\),查询一段区间内 \(dep\le dep_p+k+1\) 的个数与和即可。
[GDOI2016]疯狂动物城(DSY)
拆成 \(u\) 到 \(\operatorname{lca}\) 与 \(v\) 到 \(\operatorname{lca}\) 两部分。
P4396 [AHOI2013]作业
莫队+值域分块 / 分块+值域分块 / 线段树套平衡树 / 树状数组套权值线段树 / CDQ
[XSY-树结构]消防站
大意:给定一棵树,在其中取两个点 \(p_1,p_2\) 使得 \(\max_{x=1}^n \min(dis(x,p_1),dis(x,p_2))\) 最小,求该最小值。
Lemma.最优的消防站 \(p\) 一定在直径上,否则把 \(p\) 往直径的方向挪只会使答案变得更优(直径的长度是树上最长的,感性理解)。
枚举一个“断边”,切断该断边后原树分为左右两块,对它们分别考虑,问题转化为在一棵无根树中取一个根使深度最小。由 Lemma 取的两个根都在原树的直径上,断边在取的两个根间的路径上,所以断边在原树直径上。
我们把直径拉直,再把原树的其它节点挂在直径下方,从左到右扫一遍直径枚举断边,发现在更新断边的时候可以 \(O(1)\) 更新当前左边块最优的消防站,同理从右到左扫一遍,最后再次枚举断边取两边答案的 \(\max\) 中的 \(\min\) 即可。
P2680 [NOIP2015 提高组] 运输计划
要清的边一定在 \(m\) 条路径中最长的之中。设 \(l\) 为该条路径长度,枚举清边 \(k\) 设其长度为 \(w\),再设不经过 \(k\) 的所有路径中最长的之长度为 \(mx\),显然设置该边为清边的答案为 \(\max(l-w,mx)\)。问题转化为对所有边求 \(mx\)。每条边树剖后转化为 \(O(\log n)\) 个 dfn
区间,这些区间没有覆盖到的 dfn
区间也只有 \(O(\log n)\) 个,在线段树上用该路径长度更新之即可。
P1600 [NOIP2016 提高组] 天天爱跑步
对于给定的每个 \((s,t)\),设 \(a=\operatorname{lca}(s,t)\)。枚举所有观察员 \(u\),对于所有 \((s,t,a)\),分情况讨论(\(bg(x)\) 和 \(ed(x)\) 分别为以 \(x\) 为根的子树的 \(dfn\) 的起终点):
\(u\) 在 \(\operatorname{path}[s,a]\) 上时,\((s,t,a)\) 对 \(u\) 有贡献当且仅当如下的三个条件同时成立:
\(u\) 在 \(\operatorname{path}(a,t]\) 上时,\((s,t,a)\) 对 \(u\) 有贡献当且仅当如下的三个条件同时成立:
满足两者的 \((s,t,a)\) 不会重复,分别求解即可,问题转化为三维数点。以第一种情况为例。
\(\forall d\in[1,n]\),将所有 \((s,t,a)\) 中满足 \(dep_s=d\) 者的 \(dfn_a\) 存下来,接着以离散化后的 \(dfn_a\) 为版本号、\(dfn_s\) 为记录对象建主席树,枚举 \(u\) 时在对应树上查 \(bg(u)\) 与 \(ed(u)\) 间值的个数即可。第二种情况同理。
时空复杂度均为 \(O(n\log n)\),常数极大,估摸着目前能排到最劣解前10。
LOJ149. 01 分数规划
二分 \(ans\),对所有人的 \(a-b\times ans\) 排序后贪心选取最大的 \(k\) 个,如果它们的和 \(>0\) 那么存在比当前 \(ans\) 更大的解,反之亦然。
P3241 [HNOI2015]开店
设 \(c=\sum[a_i\in[l,r]]\),答案为
前两项好搞。对于第三项,以离散化后的 \(a_i\) 为版本号建主席树,树剖,每个点到根的路径上用主席树打上一个标记,每次询问的答案即为
HDU4734[XSY-数位DP] F(x)
自然想到 \(dp\) 第二维记录除后 len
位外的前面的数位产生的贡献总和,然而每组数据都需要 memset
时间爆炸;第二维改记前面的数位给 \(f(B)\) 加上贡献后剩余可用的值 就可以做到一个 \(dp\) 数组用到底,不需要 memset
(即我们消除了 \(A\) 的不同对于 dp
的影响)。绝妙,遂记下。
LOJ10168[XSY-数位DP] 恨6不成妻
平方和这种范围贼大的明显应作为 dp
结果而非参数。考虑记录后 len
的平方和,发现要转移到前面(当前维护的为 \(\sum a^2\)):
于是每个 dp
结果中除平方和 \(sqs\) 外,额外记录所有数的和 \(s\) 和个数 \(c\),参数放一个 “ 当前数字和 \(\bmod 7\) ” 和 “ 当前组成的数 \(\bmod 7\) ” 即可。
P5842 [SCOI2012]Blinker 的仰慕者
\(K=0\) 即求 \([A,B]\) 中含 \(0\) 的数字的和,很逊。
每个数位上只有可能是 \(0\sim 10\) 的正整数,其中质数只有 \(2,3,5,7\),于是枚举每个质数参与乘法的指数,然而空间达到 \(2\times 19\times \log_2 10^{18}\times \log_3 10^{18} \times \log_5 10^{18}\times \log_7 10^{18}\approx 49558080\),就算全开 int
也需要约 \(189\operatorname{MB}\) 的空间,显然不够。这个算法不优,因为某些极端情况(如 \(2,3,5,7\) 的指数均取最大值)是根本取不到的。看上去没有优化空间了,于是我们转换思路,直接枚举 \(10^{18}\) 内可以表示为 \(2^a\times 3^b\times 5^c\times 7^d\) 的形式的所有数,可以发现它们总共只有 \(66061\) 个,将它们排序后把 \(\frac{K}{\text{当前确定的所有数的乘积}}\) 在 \(nb\) 中的位置作为参数(用了一个小 trick,不用每次 memset
了)。转移如果在 dfs
中二分复杂度会炸,预处理一下就行了。
P3303 [SDOI2013] 淘金
设 \(c_i=\sum_{j=1}^n [f(j)=i]\),一个显然到我花四十分钟才搞懂的结论是 \((x,y)\) 的答案即为 \(c_x\times c_y\)。对着这段代码盯一会儿就理解了。
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) ++a[f(i)][f(j)];