例 1. \(\text{[Code+#7] }\)神秘序列
直接考虑原序列是不便的,考虑题目中 "在 \(n+k\) 次操作之后,序列变为全零序列",于是可以逆向思考。
那么每次操作就变成了:选择 \(a_i=0\) 的 \(i\),将 \(a_i\leftarrow i\),然后将满足 \(j\in[1,i)\) 的 \(a_j\) 减去一。这看上去仍然不好操作,但事实上,每次 \(i\) 的选择是固定的。对于 \(i=1\),会在第 \(1,3,5,\dots\) 这些次序操作,为啥捏?考虑只要是不操作 \(1\),\(a_1\) 必减一,所以结论得证。类似地,对于 \(i=2\),去掉操作 \(1\),就可以规约到操作 \(1\) 的情况,隔两次操作必操作一次。
通过这个结论,我们可以用操作次数求得对应序列长度,就像这样:
int getLen(int mid) {
ll s=k+mid; int ans=0;
while(s) {
++ ans;
ll num = s/(ans+1)+(s%(ans+1)!=0);
s -= num;
}
return ans;
}
问题是,getLen() 的复杂度是多少?事实上是 \(\mathcal O(\sqrt k)\) 的。考虑总操作次数为 \(s\),经过位置 \(1\) 后变成 \(s/2\),经过位置 \(2\) 变成 \((1-1/3)\cdot s/2=s/3\),依此类推……当经过位置 \(p-1\) 之后,操作次数变成了 \(s/p\),此时若满足 \(s/p<p+1\),就说明之后每次操作次数减少的值只能为 \(1\),同时由于位置每次只增加 \(1\),也可以解出 \(p\) 大概在 \(\sqrt s\) 也即 \(\sqrt k\) 范围。所以之后 \(s/p\) 每次减一也是 \(\mathcal O(\sqrt k)\) 的。总共也是 \(\mathcal O(\sqrt k)\) 的。
那么,我们二分序列长度,用其加 \(k\) 得到操作次数,然后使用 getLen() 求这个操作次数对应的真实序列长度,再和二分值作比较即可。为啥又可以二分呢?感性证明一下,增长序列时,越到后面花费的操作次数肯定越多,又因为 \(k\) 为定值,所以一定是先开始真实序列长度大于二分值,逐渐相等,接着二分值超过真实序列长度。我们二分相等点即可。
总复杂度 \(\mathcal O(\sqrt k\log k)\).
例 2. \(\text{[ZJOI 2014] }\)璀灿光华
感觉这题建立正方体的方法挺妙的~先随便选一个度数为 \(3\) 的作为原点,以它为基准 dfs() 出两条棱,接下来可以利用一个性质拓展:新点的编号一定是旧点的邻接点中出现次数最多的点。
最后枚举发光点的方向再 dfs() 一遍即可。
例 3. \(\text{[HNOI 2009] }\)梦幻布丁
链表好题。其实像这种合并有永久性的题目都可以考虑使用启发式合并。对于此题,每种颜色都维护一个链表,假设将颜色 \(x\) 变成 \(y\),必须花费 \(x\) 集合的大小的复杂度……吗?实际上,我们可以将 \(y\) 合并到 \(x\) 上,然后把之后修改的 \(x\) 看成 \(y\),\(y\) 看成 \(x\)!
另外也可以用线段树合并,每种颜色维护一个线段树,对于每个区间维护最左/右的点的编号,向上合并的时候检查编号是否相邻即可快速计算段数。
例 4. \(\text{[CQOI 2012] }\)局部极小值
当时看到 \(n\le 4,m\le 7\) 的鬼畜数据范围人都傻掉了……网络流用这个范围都嫌小啊😅也许只能是状压 \(\mathtt{dp}\) 了吧。
从小到大填数。设 \(dp_{i,s}\) 表示填到了数字 \(i\),且目前已经填入数字的极小值点所构成的集合为 \(s\) 时符合要求的方案数(为啥不记录非极小值点的状态后文有提及)。考虑转移:
- 将数字填入极小值点。此时枚举不属于 \(s\) 的极小值点 \(j\),转移为 \(dp_{i+1,s|2^j}\leftarrow dp_{i,s}\).
- 将数字填入非极小值点。这种情况相对复杂,因为需要保证非极小值点周围的极小值点均已填上数字。具体实现时,令 \(\text{all}=nm\),再遍历所有 未填数 的极小值点,减去此类极小值点的个数与和它们相邻的非极小值点个数,这样剩下的 \(\text{all}-i\) 就是可供选择的非极小值点数。容易发现这个值只与 \(s,i\) 有关,所以并不需要记录非极小值点的状态。
求出 \(dp\) 数组后就做完了……吗?题目中有一个异常隐晦的限制:整张图中的极小值点只能为给定点。而我们计算出来的结果显然是 至少 为给定点的情况。这时就可以祭出我们的 二项式反演 中的形式贰!不妨令 \(f(i)\) 为至少 多 \(i\) 个极小值点的方案数,\(g(i)\) 为恰好 多 \(i\) 个极小值点的方案数。最后只需要求 \(g(0)\) 即可。仔细分析这个柿子:
对于此题可以手动组合数,也就是 dfs() 枚举所有 \(s\)(题目给定最小值点的集合)的超集 \(t\),将 \(dp_{nm,t}\) 加上 \((-1)^{|t|-|s|}\) 系数贡献即可。
例 5. \(\text{[PA2015] Siano}\)
一点题外话:笔者先开始写了个二分套 \(\rm zkw\) 的鬼畜算法,最后和线段树完成拼盘……反正两只 \(\log\) 慢得像恰了 \(\text {sh*t}\) 一样😂。
言归正传,本题需要完成三个操作:查找高度 \(\ge h\) 的一段后缀;查询一段区间的高度和;更改一段区间的高度为定值。
对于第一个操作,可以对每个区间维护右端点的高度 \(\rm comp\),然后开 \(\text{tag},\text{add}\) 分别维护区间赋值与区间加(时间的变化),用 \(h\) 维护高度。总复杂度 \(\mathcal O(n\log n)\).
还有一个 \(\mathcal O(n)\) 的做法,但是题解真的太太太太太长了,所以就不研究了 qwq.
例 6. \(\text{[ZJOI 2016] }\)旅行者
说句闲话:"网格图想分治的技巧已经是老生常谈。"
想到分治这题就非常暴力了:递归传入矩形 \(r,c\) 和询问区间 \([l,r]\). 选择较小一条边作为切割边,切割边上的每个点都跑一遍在矩形内的 \(\rm dijkstra\),然后按照切割边合并询问的答案。接着分治成两个矩形,将全包含在某个矩形的询问放入那个矩形的分治。时间复杂度 \(\mathcal O((S\sqrt S+q)\cdot \log S)\)(\(S\) 是矩形面积),具体证明如下:
设当前矩形面积为 \(S\),一个重要的结论是切割边 \(\le \sqrt S\),那么就有
运用主定理可证。
考虑询问的部分,由于每次递归矩形面积减半,所以只有 \(\log S\) 层,复杂度为 \(\mathcal O(q\log S)\).
例 7. \(\text{[APIO 2016] }\)划艇
一些牢骚:什么时候才能会做 \(\mathtt{dp}\) 题啊……
由于 \(a_i,b_i\) 极大,可以想到离散化。如何离散化也是有讲究的:把所有区间变成 \([l,r)\) 的形式,这样即可保证两两区间不交,这样就不会出现划艇数被两个区间包含的不方便计算的情况(输入时将 \(b_i\leftarrow b_i+1\))。可以发现,所有学校的划艇数区间都可以被划分成若干个连续的新区间,而新区间规模是 \(n\) 级别的,大大降低了复杂度。
类似最朴素的 \(\mathtt{dp}\),设 \(dp_{i,j}\) 为前 \(i\) 所学校,第 \(i\) 所学校参赛且派遣划艇数在区间 \(j\) 的方案数。这里还需要介绍一个引理:
\(\cal{Lemma}\):若要从 \([0,L]\) 中选出 \(n\) 个数,其中 非零数 严格递增,则有 \(\binom{n+L}{n}\) 种可能。
\(\cal{Proof}\):对于不含零的情况,答案就是 \(\binom{L}{n}\). 现在假设在序列 \(\{1,2,\dots,L\}\) 之前加上 \(n\) 个零,从新序列中选 \(n\) 个数的方案数即为 \(\binom{n+L}{n}\),这实际上等价于原问题,因为你可以将取第 \(i\) 个零看作第 \(i\) 次取零。
回到计算 \(dp_{i,j}\) 的原问题,因为第 \(i\) 所学校必须参赛,所以至多只能插入 \(m-1\) 个零,其中 \(m\) 表示挑选划艇个数在第 \(j\) 个区间的学校的数量。方案数即为 \(\binom{m-1+L}{m}\). 那么转移方程即为
(突然发现强制第 \(i\) 所学校必须参赛挺有讲究的,这能保证这个转移不算重 qwq.
但是这个转移方程怎么降到 \(\mathcal O(n^3)\) 呢?一个观察是在 \(i,j\) 确定的情况下,对于相同的 \(p\),对应的 \(m\) 是一样的,所以考虑优化 \(k\) 这一维。这就是个前缀和 \(\sum_{k=1}^{j-1}dp_{p,k}\) 的形式,前缀和优化即可。
例 8. \(\text{[CEOI 2019] Dynamic Diameter}\)
欧拉序维护动态直径。首先维护 "一进多出" 的欧拉序序列,那么区间 \([l,r]\) 的直径可以表示为
需要注意 \(k\) 与 \(i,j\) 可以重合,这代表某节点向根的一条链的情况。接下来,我们考虑多记录一些值来减少柿子的变量:记 \(\text{lmx}_{l,r}\) 为在 \([l,r]\) 区间中,\(d_i-2d_j(i\le j)\) 的最大值,\(\text{rmx}_{l,r}\) 同理,这样我们就可以合并直径了。对于修改,实际上就是子树 \(d\) 整体加,也就是区间加,再增加 \(\rm la\) 标记即可。
例 9. \(\text{[CQOI 2017] }\)小 Q 的表格
还是挺神的 qwq.
首先化简题目中给出的柿子:\(f(a,a+b)/(a+b)=f(a,b)/b\). 我们发现这个等式是可以延展的,就是第二项加减若干个 \(a\),所以同时这个变换是可逆的。于是,我们可以得到 \(f(a,b)/b=f(a,b\bmod a)/(b\bmod a)\). 由于题目中还有 \(f(a,b)=f(b,a)\),此时你可能会想到,辗转相除法!然而如果直接续写这个等式,你会得到 \(f(b\bmod a,a)/(b\bmod a)\),这看上去和之前的形式不太一样。
事实上,我们可以改一改这个等式
这样形式就规整了。于是可以进行辗转相除,得到上面这一坨等于 \(f(\gcd(a,b),k\cdot \gcd(a,b))/(k\cdot \gcd^2(a,b))\). 这是因为辗转相除最后的形式是 \((\gcd(a,b),0)\),再往回倒推一步得到上面的结果。显然,再应用上文的结论,这一坨还等于 \(f(\gcd(a,b),\gcd(a,b))/\gcd^2(a,b)\). 所以可以得知 \(f(a,b)=(ab/d^2)\cdot f(d,d)\),其中 \(d=\gcd(a,b)\).
我们再推一下答案
最后一步是因为有 \(\sum_{i=1}^n i\cdot [\gcd(i,n)=1]=(n\cdot \varphi(n)+[n=1])/2\). 这里由于 \(j>i\) 的对称性乘以二,但同时 \(i=j\) 的情况被多算了一次,但事实上只有 \(i=j=1\) 时才有贡献,就得到上面的柿子。
后面一坨可以 \(\mathcal O(n)\) 预处理。考虑修改时只用对 \(\gcd(a,b)\) 单点修改,但我们需要维护前缀和。用树状数组查询则会带上 \(\log\),查询复杂度飙升为 \(\mathcal O(m\sqrt n\cdot \log n)\),修改复杂度却是 \(\mathcal O(m\log n)\) 的,这不太平均。所以考虑用分块优化复杂度,这样整体复杂度是 \(\mathcal O(m\sqrt n)\) 的。
例 10. \(\text{[JOI Open 2019] }\)三级跳
喵喵题。直接考虑 \(a,b,c\) 似乎有些困难,假设确定了二元组 \((a,b)\) 或者 \((b,c)\),我们事实上可以算出符合条件的 \(c\) 或 \(a\) 在哪个区间。不过确定二元组 \((b,c)\) 是不可行的,因为组数太多了;但对于二元组 \((a,b)\) 有一个很棒的性质:区间 \([a,b]\) 中,如果存在 \(i\) 使得 \(v_i\ge v_a\or v_i\ge v_b\),那么这个二元组就不优,可以被舍弃。
我们不妨固定 \(a\) 来思考这个问题,这实际上就是找到在 \(a\) 之后第一个大于等于 \(v_a\) 的数,在这之前的数字都可以和 \(a\) 配对。等等,这好像有点像……单调栈?于是用单调栈维护所有 \((a,b)\),二元组个数就是单调栈的复杂度。最后用线段树维护 \(c\),从右到左加入二元组然后查询即可。
例 11. \(\text{[CTSC 2014] }\)图的分割
说句闲话:不知道为啥这题过不了……不过题解也过不了,那就说明 —— 我是对的(?
首先翻译一下 \(M(C)\) 的定义:\(C\) 形成的诱导子图的最小生成树的最大权值。这题有个结论:将 \(w\) 从小到大排序,对于边 \(\langle u,v,w\rangle\),如果 \(w\le \min\{M(f_u)+Z(|f_u|),M(f_v)+Z(|f_v|)\}\),那么就连接这条边,将 \(f_u,f_v\) 合并成一个集合。
首先我们证明,如果想要得到半完美分割就一定要连接这条边。首先如果 \(f_u,f_v\) 之后仍然没有与连通块连边,这个关系仍然满足,就得不到半完美分割;如果任意一个与连通块连边,由于边权从小到大排序,所以 \(M\) 值一定 \(\ge w\),而 \(Z\) 一定大于零,所以还是得不到半完美分割。
接下来只需证明这是完美分割。其实这也简单,之前我们已经证明每条边都是必选的,不选就无法得到半完美分割,所以将任意连通块分割都无法得到半完美分割。
例 12. \(\text{[NOI Online 2022] }\)讨论
喵喵题,感觉不看题解真的想不到这个贪心啊……
这题有个非常重要的 \(\rm observation\):假设存在 \(S_x\subseteq S_y\),那么对于 \(\forall z,|S_z|\ge |S_y|\),\(S_x\) 都没有任何用处。
所以不妨将集合按照大小从小到大排序。为了利用上面的性质剪枝,我们在遍历第 \(i\) 个集合时将其与 \(j(j<i)\) 集合匹配。枚举第 \(i\) 个集合的元素,对于每个元素 \(x\) 搞一个 \(pre_x\) 数组表示上一个有这个元素的集合,\(\text{ban}_i\) 表示这个集合是否被遍历过(显然,如果被遍历过还未出解的话,这个集合没有任何用处)。
例 13. \(\text{P5283 }\)异或粽子
考完后才发现这题不需要可持久化 \(\rm trie\)……因为 \(a_i\oplus a_j(i<j)\) 从 \(i\) 查询到还是从 \(j\) 查询到都是一样的,所以只需要增加至查询 \(2k\) 个点对即可。注意特判 \(a_i\oplus a_i\) 的情况。
例 14. \(\text{P5290 }\)春节十二响
说句闲话:看完这题的题解感觉自己的破题能力好弱,这样的送分题压根就没触及到贪心的思想……感觉自己要被送退役了。
对于 \(n\le 16\),可以使用搜索或者是状压 \(\mathtt{dp}\),这里还是讲一讲这个状压:首先用 \(\mathcal O(2^n)\) 处理出 \(f(S)\) 表示选点集合为 \(S\)(\(S\) 必须合法)时占用的 \(\rm cpu\),具体可以枚举二进制的最后一个 \(1\);然后直接 \(\mathcal O(3^n)\) 暴力合并这些选点集合即可。
题目最重要的转化是将合并子树变成 从大到小 合并两个堆。然后用启发式合并。所以贪心就是要多试试啊。另外一个非常妙的点是这题的复杂度是 \(\mathcal O(n\log n)\) 而并不是常见的 \(\mathcal O(n\log^2 n)\)!原因是,每个节点的堆大小实际上是这个节点开始向下的最长链长度,我们可以将这棵树长链剖分(这样所有长链的总长是 \(\mathcal O(n)\) 级别的),实际上,每条长链只会在顶端的轻边计入复杂度,所以实际上每个点只会被遍历 \(1\) 次,加上堆的复杂度就是 \(\mathcal O(n\log n)\) 的。
例 15. \(\text{P6619 }\)冰火战士
一些闲话:当别人都在思考如何将 \(\mathcal O(n\log^2 n)\) 的二分优化到 \(\mathcal O(n\log n)\) 时,我还在写我的乱搞三分,可能这就是人与人之间的差距吧 /kk. 而且还写了不下三个锅,还好最后搞到了 60 分。如果想看看我的三分可以戳这个:\(\rm Link.\)
首先对题意的转化是求一个凸函数的顶点,看上去可以三分,然而 三分要考虑平台!而此题平台显然是很充裕的,那咋办呢?这题的凸函数其实非常特殊 —— 它由两个单调函数组成。所以我们可以换一个角度描述顶点:令 \(f(i)=\text{ice}(i)-\text{fire}(i)\),可以发现 \(f\) 是单调不降的!于是得到了一个 \(\mathcal O(n\log^2 n)\) 的做法。具体而言,先找到最大的 \(f(p)<0\) 的点 \(p\),然后从 \(p+1\) 开始尝试找和 \(p+1\) 的函数值相等的最右点。显然取最右点与 \(p\) 之间的最优解就是答案。
考虑进一步的优化,我们可以直接在树状数组(\(\text{Fenwick Tree}\))上二分。从 \(p=0\) 开始二分,每次查询 \([p+1,p+2^i]\) 的区间(\(i\) 从大到小枚举,这样能保证 \(2^i\) 是 \(p+2^i\) 的 \(\rm lowbit\))即可。
浙公网安备 33010602011771号