world.construct(me);
标题化用自 《world.execute(me);》。
0 引言
0.1 所谓构造题
构造题这种题型,不同于常见的计数或最优化问题,它一般要求选手给出任意一组符合一定要求的答案,即使冠以“最优的答案”之名,往往求得“最优值”比较轻易,构造题的难点集中于方案构造。而正如 OI Wiki 所介绍的:
构造题一个很显著的特点就是高自由度,也就是说一道题的构造方式可能有很多种,但是会有一种较为简单的构造方式满足题意。看起来是放宽了要求,让题目变的简单了,但很多时候,正是这种高自由度导致题目没有明确思路而无从下手。
构造题普遍的高思维难度以及老少咸宜(?)的代码实现难度,使得它在竞赛中的考察越来越频繁(NOIP 2020 T3, CSP-S 2021 T3, 所以下一道是……)。因此,雨兔 秉承直面恐惧的精神, 为大家整理归纳了一些构造题与其思想精髓,希望能帮助大家切掉 T3。(开始押题.jpg
0.2 重点是动机 (motivation)
如 Tiw 所言,构造题的思考方式和传统 OI 题的思考方式并没有很大隔阂。只不过,构造题将重心放在了思维而非算法优化,这导致思考过程中找不到“为靠向某某算法进行转化”的步骤,也导致看了题解后觉得“这条思考链并不长,而我想不到”继而自闭的后果,这很正常。面对构造题,应当做好抛弃前人封装好的所谓“算法”的觉悟,自己走出思考链,哪怕并不长,步步皆思维。正如你被要求造出高效维护区间的数据结构——在你学线段树之前。
而在写一道构造题的题解时,我觉得重点是动机——具体的构造方法是谜底,只有让人拍案叫绝的功能,但真正领悟一道题的价值,需要学习的是从谜面或思考,或打表,或猜测……在人类智慧的努力下得到谜底的过程。也希望大家写构造题题解的时候能够注意,读者真正想要的,是“为什么这么想”。
扯了那么多,我们开始叭。为了让例题发挥最大功效,我会先放题和具体解法,最后给出一个章节的总结。
1 实践出真知
1.1 「CSP-S 2021」「洛谷 P7915」回文
1.1.1 题目大意
Link.
1.1.2 解题过程
——手玩是正道。
读完题,抓住重点:每个数恰好出现两次 ⇒ 第一次操作数字 x 的时刻决定了第二次操作数字 x 的时刻。另一方面,也应该意识到本题设的难点:双端操作。
Motivation: 我想要给双端操作加上限制,例如……固定一个点,双端队列就会变成栈。
“固定一个点”?固定最后一次操作的点是最牢靠的……等等,根据上文的结论,固定最后一次操作的点不就是固定第一次操作的点吗?第一次能操作的点不就两个吗?!枚举一下,问题就解决了呀。
“我没这个想法?”别担心,你但凡手玩任何一组样例,哪怕只玩第一步,都能得到思路。
1.2 「ARC 110F」Esoswap
1.2.1 题目大意
Link.
1.2.2 解题过程
——样例是题解。
样例解释:
First, announce i=6. We swap P6(=5) and P(6+5)mod, turning P into 7,1,2,5,4,0,6,3.
Then, announce i=0. We swap P_0(=7) and P_{(0+7)\bmod8}=P_7(=3), turning P into 3,1,2,5,4,0,6,7.
Then, announce i=3. We swap P_3(=5) and P_{(3+5)\bmod8}=P_0(=3), turning P into 5,1,2,3,4,0,6,7.
Finally, announce i=0. We swap P_0(=5) and P_{(0+5)\bmod8}=P_5(=0), turning P into 0,1,2,3,4,5,6,7.
一方面,样例具有迷惑性,且构造题一般没有大样例,而对于小样例,出题人很可能在标算输出的解法上进行手动调整;另一方面,相信懒惰的出题人给出的方案一定程度上相似于标算解法。所以——
Motivation: 我想用“主观感知”调整并阐释出样例方案的遵循的模式。
观察本题样例解释,发现三次 p_i=5,一次 p_i=7。考虑到样例的迷惑性,我们尝试让对 p_i=5 的操作挨在一起。交换第一步和第二步,发现操作序列仍合法。
接下来,我们强行解释该操作序列的内在逻辑:
- 希望 p_7=7,反复操作 p_i=7 直到 p_7=7;
- 希望 p_7=7\land p_6=6,反复操作 p_i=6(样例中不需要操作),直到不满足 p_7=7 回到第一步,或满足 p_6=6;
- 希望 p_5=5\land p_6=6\land p_7=7,操作同上。
- ……
综上,交换策略为
- 选择不满足 p_i=i 的最大的 p_i 进行交换直到序列升序。
戏剧性的是,这波分析猛如虎,得到的并不是题解做法,也并没有证明构造的复杂度,但是我确实以这种构造方法通过了本题。这其实也反应了构造题给选手带来的机会——出题人难以想象到所有非正解做法并造出对应的 hack 数据,也就是说,扯淡的思路甚至更有可能得大分。
1.3 「多校联训」子集
1.3.1 题目大意
题意简述
将 1\sim n 划分为 k 组,每组大小相等且数字和相等。多测,\sum n\le10^6。1.3.2 解题过程
——暴搜是艺术。
2\mid\frac{n}{k} 的情况显然,不断取最大最小值匹配即可。而就算 2\nmid\frac{n}{k},我们也不想浪费那么简单的构造方法——
Motivation: 我想尽可能快地将问题化归为 2\mid\frac{n}{k} 的情况。
联系样例 n=9,k=3 有解,我们尝试用“最快”的方法化归——将 1\sim 3k 分给集合,每个集合分 3 个,使得每个集合等和。这个……手玩有点困难啊。
Motivation: 我手玩不动。
写一个暴搜,大概是依次枚举 1\sim 3k,再枚举数字填入集合 1\sim k。我记得我写出来搜到的第一组 k=5 时的解是:
1 ? ?
2 ? ?
3 ? ?
4 5 ?
? ? ?
这直接给我整不会了啊,长得那么丑(我甚至记不得具体长相),怎么找规律?注意到第一列 1 2 3 4
都齐了,那把 5 放第一列应该也有解?
Motivation: “字典序最小”的解并不优美,我想人为给它加上限制。
这样写了之后,第一列是 1 2 3 4 5
,也有一堆解,但其中字典序最小的解的后两列还是乱麻。这个时候,我随便翻了翻所有的解,看到一个:
1 9 14
2 10 12
3 6 15
4 7 13
5 8 11
这个第二列就有规律了:从中间位置开始,6 7 ...
,接着再从最顶上开始,9 10 ...
,第三列自然可以通过总和减出来。再依次规律验证一下 k=7 的情况,发现同样适用,就结束啦。
1.4 小结
为什么把“实践”放在第一节讲?当然是因为
劳动是人类的本质活动。 ——马克思
构造题难以下手,那就应当尝试从简单下手,如上文中对样例、样例解释和暴搜的灵活应用,虽然并不是严谨的“题解”,但应用在赛场上,不仅能帮助我们稳定心态,高效思考,也能在想不出正解时及时止损。
2 拼盘得正解
2.1 「CF 1450C2」Errich-Tac-Toe (Hard Version)
2.1.1 题目大意
Link.
2.1.2 解题过程
——别忘记小奥。
首先,就个人经验来说,不要妄想用调整法在棋盘上直接搞,如果你过了当我没说。(
Motivation: 三连棋我并不熟悉,但如果是“二连棋”……这是二分图?
想一想若是二连棋,我们可以把棋盘交替黑白染色,若令所有黑格只能是 X
,所有白格只能为 O
,那么必然平局;若令所有黑格只能是 O
,所有白格只能是 X
,也必然平局。但是,这两个方案都完全不能保证操作次数呢……
Motivation: 我想利用这两个并不一定正确的方案。
挣扎一下?如果一种方案不行,就采用另一种……等等,两种方案操作次数之和为 k,根据鸽巢原理,必然有一种操作次数 \le\lfloor\frac{k}{2}\rfloor!
同理地,对于三连棋,我们直接三染色——令 (i,j) 的颜色为 (i+j)\bmod 3,构造三种方案使得操作之和恰为 k,就必然有一种操作次数 \le\lfloor\frac{k}{3}\rfloor。
2.2 「CF 1364D」Ehab's Last Corollary
2.2.1 题目大意
Link.
2.2.2 解题过程
——NPC Solver.
Motivation: 这类题想必大家见过了,常见的想法是:以一个问题无解为条件,解决另一个问题。
首先,如果无环,直接树上二染色选其中一个集合;否则,考虑极小(注意不必要是最小)的环的大小 s:
-
若 s\le k,回答问题二。
-
否则 s\ge k+1,由于 s 是极小的环,所以环内没有弦,那么隔一个选一个,能选出 \lfloor\frac{k+1}{2}\rfloor 个,也即是 \lceil\frac{k}{2}\rceil 个点。
两个不保证正确性的解法构成互补关系,让程序自己挑一个舒服的来做叭。
2.3 小结
从鸽巢原理到 NPC 二选一(虽然 2.2 的第二问似乎是 P 的),我们应当把一些“显然的错解”记录下来,想一想它们的适用条件,说不定一个拼盘就拼出了全集。
3 博弈出题人
3.1 「Gym 102900B」Mine Sweeper II
3.1.1 题目大意
Link.
3.1.2 解题过程
——抓隐含条件。
请问你会算一个局面下所有格子数值的和吗?——会呀。
既然如此……出题人告诉我 B 干嘛?!
Motivation: 出题人告诉了我与问题不直接相关的 B,那我就用 B 作为条件来构造 A。
同时,发现把一个局面的雷和空格反转,数值和不变(每对雷-空格的关系仍存在),那么 A 就能变成 B 和 \lnot B 中的一个。联系 2.1,两个方案反转总数是 nm,根据鸽巢原理,必然存在一解。
3.2 「CF 1205D」Almost All
3.2.1 题目大意
Link.
3.2.2 解题过程
——猜系数由来。
你一看这个 \lfloor\frac{2n^2}{9}\rfloor,就该想到 \frac{2}{9}=\frac{1}{3}\cdot\frac{2}{3}。 ——Tiw
出题人在这类题目里留下了奇怪的系数,看着很毒瘤,但请相信,那是出题人给你留下的提示。
希望不会有出题人帮你把 2/9 放松到 11/53 之类的玩意儿。
Motivation: 我想凑出 \frac{1}{3}\cdot\frac{2}{3} 的情景。
直接数字观察太科幻了,想一想这个乘法,很可能是乘法原理,也就是说……把树划分为两个部分,仅考虑两部分间的路径?
划分本身并不困难,但理应做到越平均越好,注意 \frac{2}{9} 应是下界。
Motivation: 子树大小平均 \Rightarrow 重心。
若能在重心上,我们能够将一些子树划为重心所在连通块作为第一部分,另一些子树一起作为第二部分,两部分有一个公共“顶点”重心,就能用乘法原理了。
事实上,这是可行的。不断合并最小的两棵子树即可。最“不平均”的情况即有三棵大小相等的子树,最终得到 \frac{1}{3} 和 \frac{2}{3}。
另一方面,我们还需要实现“乘法原理”。根据前文的铺垫,乘法中的“单个物品”是结点到重心的的距离。也就是说,设 A 是第一部分中结点到重心的距离集合,B 是第二部分中结点到重心的距离集合,我们希望 A+B=\{a+b\mid a\in A,b\in B\}=\{1,2,\dots,|A|\cdot|B|\}。
这是……进制?
不妨设 |A|\le|B|,则令 A=\{1,\dots,|A|\},B=\{(|A|+1),2(|A|+1),\dots,|B|(|A|+1)\},不难发现我们达成了目标。
此时,问题已然转化为:为边赋权,使得每个结点到根的距离构成某集合 C。这个问题比较简单,把 C 中最小的值赋在根的某一条邻接边,以此分为两个子树归纳构造即可。
如果你去看官方题解,你会发现这里给出的解题过程几乎是反过来的。这个例子放在这里的作用是强调系数观察的重要性。实际解题过程中,思路因人而异,正推、逆推或者双向搜索各有优劣,看自己的习惯灵活使用吧。
3.3 小结
构造题难在“自由”,那么出题人给出的复杂限制不失为一种提示与帮助。从条件入手,从限制系数入手,结合问题背景合理联想想象。可以说,做构造题的另一种思路便是:猜猜出题人怎么做。
4 模块建大楼
4.1 「CF 1368C」Even Picture
4.1.1 题目大意
Link.
希望你解决 OneInDark 的加强题目:限制 k\le 3n。不过 Rainybunny 更希望你做到更优。
4.1.2 解题过程
——局部到整体。
那什么,确实很难不被推翻。(
Motivation: 我想构造一个贡献率高的结构。
什么贡献率高嘛?一坨点呐。
..#..
.###.
#####
.###.
..#..
(如果你构造的是正放的正方形,也会为了合法而调整成这种形状。)可惜的是,我们还是难以把这一坨弄合法。稍微变形一下?
..##.. ...##...
.####. ..####..
###### => ########
.####. #.####.#
..##.. #..##..#
#......#
########
首先,局部层面,这个模块自身是优秀的;其次,这个模块是可通过简单调整合法的;更有趣的,整体上说,这个模块可连续使用:
..##.....##..
.####...####.
- - - ############# - - -
.####...####.
..##.....##..
什么意思呢?有点像倍增:我们每次构造一大坨,使得其贡献恰好不超过 n,然后将 n 减去贡献,继续构造。可以发现,这种构造方法所需的 #
的个数为 n+\mathcal O(\sqrt n),当 n=300 时仅需 442 个 #
。
当然,构造方法多种多样,希望得到更好的做法呢。
4.2 「OurOJ #46544」漏斗计算
4.2.1 题目大意
题意简述
定义一个运算结点 u 有两个属性:当前容量 x_u、最大容量 V_u。提供以下单元操作:
-
I
读入一个整数 x,令新结点 u=(x,x)。 -
F u
装满 u 结点,即令 x_u=V_u。 -
E u
清空 u 结点,即令 x_u=0。 -
C s
令新结点 u=(0,s)。 -
M u
令新结点 v=(0,x_u)。 -
T u v
不断令 x_u\leftarrow x_u-1,x_v\leftarrow x_v-1 直到 x_u=0 或 x_v=V_v。
构造不超过 10^4 次操作的一个运算方法,输入 a,b,输出 ab\bmod 2^{18}。
0\le a,b\le 10^5。
4.2.2 解题过程
——模块化编程。
先从条件入手,发现给的运算实在垃圾的离谱,而且只有一个二元运算 T(u,v)。
Motivation: 只有一个二元运算,所以我想用且仅能用它来实现基本的“逻辑判断”功能。
再从问题考虑,不难想到用龟速成求 ab,继而取模可以化简为“若某值大于 262144=2^{18},则令其减去 2^{18}。那么我们至少需要实现这些模块:
-
加法器:输入结点 u,v,输出 w,满足 x_w=x_u+x_v。
-
逻辑减法器:输入结点 u,v,输出 w,若 x_u\ge x_v,x_w=x_u-x_v;否则 x_w=x_u。
注意,模块应能够独立完成相应功能,并且足够简洁,切忌复杂化问题本身。用模块封装功能,本质上就是理清思维的过程。由于本题细节较复杂,下文讲解会佐以代码片段。
加法器比较方便:新建一个大容量点,把两个加数复制一份倒进去就好。先实现一个复制当前容量器:
inline int copyNum( const int u ) {
printf( "M %d\n", u ), ++node;
printf( "F %d\n", node );
return node;
}
再实现加法器:
inline int add( const int u, const int v ) {
printf( "C 1000000000\n" ); int res = ++node;
printf( "T %d %d\n", copyNum( u ), res );
printf( "T %d %d\n", copyNum( v ), res );
return res;
}
逻辑减法?先要判断大小关系。而 T(u,v) 之后 x'_u=\max\{x_u-x_v,0\},我们只需要判断一个结点的当前容量是否为 0。好消息是,我们能够实现逻辑非器:
inline int logicNot( const int u ) {
printf( "M %d\n", u ), ++node;
puts( "C 1" ), ++node;
printf( "F %d\n", node );
printf( "T %d %d\n", node, node - 1 );
return node;
}
内部逻辑比较易懂就不讲啦。在此基础上,实现普通减法器和逻辑减法器:
inline int sub( const int u, const int v ) {
printf( "M %d\n", v ); int tmp = ++node;
printf( "T %d %d\n", copyNum( u ), tmp );
return node;
}
inline PII logicSub( int u, int v ) {
int f = logicNot( sub( u = add( u, 1 ), v ) );
rep ( i, 1, 18 ) f = add( f, f );
printf( "M %d\n", f ), f = ++node;
printf( "T %d %d\n", v = copyNum( v ), f );
return { u = sub( sub( u, v ), 1 ), f };
}
逻辑减法器返回的 first
即减法结果,second
用于求解时重复利用。
底层方法实现之后,剩下的工作就简单了:输入 a,b,计算 2a,4a,\cdots,2^{16}a,枚举 b 是否大于等于 2^{16}\dots2^0,若是,则减去,答案加上对应的权。都能用以逻辑非为基础的模块实现。
操作次数复杂度为 \mathcal O(\log^2 V)(倍增以及内部的逻辑减法),我实现的常数较大,不过封装成模块很易懂就是了。(
完整代码见 我的博客 嗷。
接下来建议挑战瓶子国和跳蚤国。(bushi
4.3 小结
正如上文所说,用模块封装功能,本质上就是理清思维的过程。从 4.2 这种真正意义上的功能封装到 4.1 所展示的“能独立实现功能,能协同组合运用”的“广义封装”,包括一些序列排序问题将给定操作组合成新的操作这种“步骤封装”,都有让问题“焕然一新”的作用。
5 生活在树上
5.1 「CF 1586E」Moment of Bloom
5.1.1 题目大意
Link.
5.1.2 解题过程
——手动加限制。
先考虑无解条件:若存在某个点 u 是奇数个询问路径的端点则无解。因为此时 u 的邻接边的总覆盖次数为奇数,与要求矛盾。
Motivation: 图上问题太难处理了,我想把它变成树。
注意这里的细节:我们先(在原问题上)判无解,再放到树上处理。因为“某棵树上有解”是“图上有解”的充分不必要条件,放到树上后尽可能保证必然有解,解法才算严谨。
变成树,那随便取一棵生成树。对于每个询问直接覆盖树上路径,结束了。在不满足上文无解条件时必然有解。
证明也比较简单:以子树角度考虑,每次把子树内向上的覆盖次数当成从父亲出发的询问,再删除子树。这能保持无解条件恒不成立。
建生成树,就像是针对图上构造题的二向箔。(
5.2 「IOI 2019」「洛谷 P5811」景点划分
5.2.1 题目大意
Link.
5.2.2 解题过程
——树上特殊点 & 性质要深挖。
Motivation: 同上,图上下不了手啊,建 DFS 树(DFS 树额外拥有“非树边均为返祖边”的性质)。记得需要证明树上无解等价于图上无解。
不妨设 a\le b\le c,显然我们让 A,B 分别连通即可。而在树上考虑,有点像 3.2,无非是把树切成两部分,让 A,B 分别能被其中一部分容纳。设切出来的一棵子树大小为 s,则 s\in[a,n-b]\cup[b,n-a]。
考虑构造?不。a,b,c 算数上的性质还没有被发现。由于 a\le b\le c,a+b+c=n,所以 a\le\frac{n}{3},b\le\frac{n}{2},继而 n-b\ge b,所以 s 只需保证有 s\in [a,n-a]。
先把能够得到答案的情况剔除于思考进程:以 r 任意结点为根的 DFS 树中,若存在子树大小 \in[a,n-a] 则可以构造,接下来,则把不存在子树大小 \in[a,n-a] 当成新条件使用。
接下来猜一猜 motivation?
Motivation: 子树大小限制 \Rightarrow 重心。
从 3.2 的“平均”到此处“限制”,可以发现所谓 motivation 可能不是“说出来就很有道理”,不然和推导没两样。正如某著名数学老师「硫氢根」所说,对已知知识要敏感,学会将性质“勾连”,“合理”联想。当然树上的重心啊直径啊你都拿出来试试也行。
设重心为 g,由上文讨论,对于 g 的孩子 u,应有 s_u\not\in[a,n-a]\Rightarrow s_u< a。同理,s_g\not\in[a,n-a]\Rightarrow s_g> n-a,即若以 g 为根,g 原来父亲所在子树的大小也 < a。
可见,不管我们怎么调整,这棵 DFS 很难再产生解了。怎么办?想起被遗忘的返祖边了吗?我们得把它们加回去。
为利用返祖性质,还是保持 r 为根不变,且仍在 g 处考虑,返祖边的功能仅仅是:将 g 的一棵子树(或其子树的子树等,但这里用不到)丢到 g 的祖先(不包括本身 g)上。令 t 为以 g 为根时父亲的子树大小,我们可以做到:令 t\leftarrow t+s_u,s_g\leftarrow s_g-s_u,其中 u 是 g 的某个孩子,并且 u 子树内拥有指向 g 祖先的返祖边。
到此,树上的分析差不多了,问题抽象成:有两个数 x,y 和一个集合 \{z_k\},你可以时 y 减去一个子集和,x 加上同一个子集和,问能否使 y\in[a,n-a]。
显然,由于初始有 y>n-a,若 \{z_k\} 中所有数的和都无法让 y 减小到不超过 n-a 的值,则无解;否则,由于区间 [a,n-a] 的长度为 n-2a\ge\frac{n}{3},而每个 z_i\in[1,a)<\frac{n}{3},所以步长足够小,我们不断令 y 减去任意一个 z_i,必然存在一个时刻 y 落入 [a,n-a],此时我们得到了解。
另一方面,我们还需要 g 处无解等价于图上无解,而考虑 g 的过程中实际上用到了图上所有边,所以仅需讨论其余结点做类似于 g 的操作也无解,这里省略不提 ,留作习题。
5.3 「CF 1214H」Tiles Placement
5.3.1 题目大意
Link.
5.3.2 解题过程
——要素要察觉。
首先呢,只有长度为 k 的路径才可能导致无解嘛。
所以呢,这个 motivation 跟上一道挺对仗的:
Motivation: 路径长度限制 \Rightarrow 直径。
注意到颜色没有本质区别,不妨设直径 (x,\dots,y) 按照 1~~2~~\cdots~~k~~1~~2~~\cdots 的周期染,此时考虑一条附在直径上结点 u 的链,链头是 v。不妨设 c_u=2,那么如果从左到右看,c_v 必须是 3;如果从右到左看,c_v 必须是 1,除 k=2 的情况外,这两个要求是矛盾的!
因此可以发现,当 k\not=2 时,对于此类形式的链 P_v,必须满足直径两端至少有一个结点,它走到这条链的尾端,长度仍 < k,这样才能避免矛盾。也即是,\min\{P_v+P(u,x)|,P_v+P(u,y)\}< k。若所有链都满足条件,在以到直径较远一端的颜色为标准继续重复周期染色即可。
5.4 小结
我们了解树远胜于图,本小节则强调了对树上要素的“联想”。DFS 树、重心、直径……人为限制构造的条件,从关键的点、路径入手考虑,反而是对问题的简化。
6 调整化腐朽
正确断句:调整/化腐朽。(OneInDark 是魔鬼!
6.1 「CF 141E」Clearing Up
6.1.1 题目大意
Link.
注意题面中对“生成树”的定义不严谨,树中不能有重边或自环。
6.1.2 解题过程
——边界要活用。
少有的我能一眼秒的构造题,泪目。
Motivation: 先找找合法条件吧。
一个显然的条件是,若能用 S
边(或 M
边)就用,仍无法让树中含有至少 \frac{n-1}{2} 条 S
边(或 M
边)则无解。
然后呢,以对 S
边的判断为例,若判断为可能有解,那么我们会得到一棵 S
边多于 M
边的生成树。能否以此构造解呢?
Motivation: 考虑到树边的可替换性,所以我想用调整法,不断加入 M
边直到树合法。
正确性证明容易,这里不提。
6.2 「CF 1396E」Distance Matching
6.2.1 题目大意
Link.
6.2.2 解题过程
Motivation: 先想想解的上下界?
从每条边的角度考虑,设边 (u,v) 删去后得到的两棵树大小为 s_u,s_v,那么这条边最多被覆盖 \min\{s_u,s_v\} 次,最少被覆盖 [2\nmid s_u] 次。据此我们可以得到答案的上界 U 和下界 D。注意求 U 时直接以重心为根就能去掉 \min。
其实此时足够我们发现一个性质:k 存在解还需保证 k\equiv U\equiv D\pmod2。怎么证明?每次研究任意调整对答案奇偶性的影响。
Motivation: 我想沿用证明奇偶性结论的思想,通过调整构造答案。
以对 U 的调整为例:每次可选最大子树内的两个点 u,v,记它们的 LCA 为 w,深度为 d_w,则将它们配对,答案减少 2d_w。注意点数为偶数保证了最大子树的大小 -2 时重心可以保持不变,所以修改后必然能够找到对应的解。由于 d 的是连续的,所以必然能够找到合适的调整方法。需要用 STL 精细实现,但这不是主要矛盾所以略过。(
6.3 小结
找答案上下界,再从其中一个向所求答案调整。这种思路让我们着眼于每次操作的影响,更容易发现操作本身的性质。
7 归纳为神奇
正确断句:归纳/为神奇。(来自 OneInDark 的建议。
7.1 「CF 1470D」Strange Housing
7.1.1 题目大意
Link.
7.1.2 解题过程
——证明即构造。
Motivation: 我能否在证明解的存在性的同时构造方案?
若图不连通显然无解。下归纳证明图连通时有解,且对于任意结点 u,存在一组解,使得 u 结点住了老师。
首先,若 V=\varnothing,显然有解。
接着,取任意一点 u,令 u 住下老师。在 G 上删除与 u 邻接的所有点及其连边,归纳构造每个连通块,同时钦定连通块中某个与删去点曾经相连的点住下老师。可见归纳总能完成。
实现的时候可以直接按 DFS 序枚举结点,能住老师就住。这里的归纳只是为了写着更方便。(
7.2 「WF 2014」「洛谷 P6892」Baggage
7.2.1 题目大意
Link.
7.2.2 解题过程
——转化向边界。
Motivation: 样例的最小操作次数为 n \Rightarrow 最小操作次数为 n。(别笑,很常用的技巧。
注意,对于最小化问题,先猜或证最小值,知道了最小值才有构造的目的性。
对于 n 较小的情况,谨遵 1.4 中的教诲,我们能暴搜打表。对于大一点的情况,我们能否设计一个归纳?
Motivation: 我想归纳。(简单明了.jpg
具体而言,我们尝试把 _ _ B A B A ... B A
变成 A A A ... B B B ... _ _
。这里简明地给出 jiangly 论文里的 例子:
_ _ B A B A B A B A ...B A B A B A
A B B A B A B A B A ...B A B _ _ A
A B B A _ _ B A B A ...B A B B A A
对于第三行中 _ _ B A B A ... B A
进行递归:
=>
A B B A A A A ...B B B _ _ B B A A
A _ _ A A A A ...B B B B B B B A A
A A A A A A A ...B B B B B B B _ _
就行了。至于“如何得到这种归纳”,鄙人只能想到手玩一种方法。(
7.3 小结
联系 4.3,我们的归纳过程本质上是将目标的分段化处理。正如万能的 DP 一样,抓住问题的“子结构”,将远在天边的目标拉向眼前。
8 走出构造题
8.1 「OurOJ 46602」糖
8.1.1 题目大意
题意概述
数轴上依次有 (n+1) 个关键点,第 0 个点为原点,第 i 个关键点的坐标是 a_i。你从 0 出发,每走一单位吃掉一颗糖,每到达一个关键点,可以以 b_i 的单价买糖,以 s_i 的单价买糖,但最多携带 C 颗糖。求到达 n 号关键点时的最小花费(假设你初始的钱足够多)。n\le2\times10^5,保证有解且答案有限。8.1.2 解题过程
——处处皆构造。
我不禁陷入沉思(?)……为什么一定要在构造题里构造?
Motivation: 算是经验之谈,我想找到这个买卖情景的等价转化。
正所谓凭空买,凭空卖,等价操作赚大钱(?)我们构造以下两个操作组合:
-
买入再以相同价格退掉 \Leftrightarrow 不买,所以每到一个关键点可以补满背包。
-
买入,篡改价格,退掉 \Leftrightarrow 低买高卖,所以我们只需要处理“买”和“退”两种操作。
这个时候贪心变得自然:背包里留下买得贵的糖。走到 i 时,维护当前背包内剩余的糖果集合 S,并保持价格单调性。将背包内所有价格 <s_i 的糖果价格篡改为 s_i(卖);将背包内所有价格 >b_i 的糖果价格改为 b_i(重新买),并用当前 b_i 补满背包;最后吃掉下一步需要的糖果(挑便宜的吃咯)。
8.2 「OurOJ 28793」硬币游戏
8.2.1 题目大意
题意概述
你有 n 组硬币,第 i 组由上到下的价值依次是 a_i,b_i,a_i,只有取走上面的硬币才能取下面的。对于 k=1,2,\dots,3n,求总共取走恰好 k 个硬币时的最大价值和。n\le10^68.2.2 解题过程
——一招解限制。
Motivation: 这个先后关系好难啊。
构造,硬币组 (a,b,a) 等价于体积为 1 的硬币 a 和体积为 2 的硬币 (a+b)。等价性显然。
接下来问题变得常规。两类体积的物品内显然选价值最大的;从选 k 体积的最优方案转移到选 k+1 体积的最优方案存在,且至多退掉一个体积为 1 的硬币。简单模拟即可。
8.3 小结
构造是一种思想。
正如你不会觉得四处乱窜的倍增算法很突兀,构造是可以无处不在的。有时候遇到题目的种种限制,不妨尝试构造,从等价题意轻松破题。
9 何为构造题
大家辛苦啦!讲题到此结束啦!我也写不动啦!
十八道花式构造下来,再思考“何为构造题”这个问题,来一个最后的交流总结吧。
9.1 构造出何物
我们构造出了什么?
是解吗?我认为那是结果,而非本质。
就我的观点,我们构造迈出的第一步,是构造限制。
我们构造出强于题目要求的限制,“自我约束”,让双端队列成了栈,让构造扫雷图成了合法性检查,让构造目标解成了构造子问题……既然“高自由度”是难点,“降低自由度”,就是我们做构造题最原始的 motivation。
很有趣吧,别的题里额外限制通向部分分,构造题里额外限制通向正解。
9.2 动机从何来
我不知道。就像 OneInDark 今天爆切 CF 3100 的构造后感慨,这个 motivation 太微弱了。动机这玩意儿多少和缘分挂钩。(
就多见题,多练题,也许吧。这种思维训练就想神经网络的 BP 一样。做一题,你有各种 motivations,在这题没用的,留一点“不好用”的感觉;在这题有用的,留一点“好用”的感觉;看题解才想到的,留一点“能用”的感觉。等训练次数够了,你试错的时间和精力消耗就少了,解题能力自然就高了。
其实我在一道道做本文的例题时,或多或少(基本上很多)地进行了规模不小的思路试错,看完题解再简单的构造题也能让人自闭。总而言之,不要怕构造题的毒打,多练,同时提炼总结每个细小的 motivation,不断积累,不断变强叭!顺便,本文也有一些没有涉及的版块,比如用欧拉路等图上模型将构造转化为图论算法问题,有空再补充(咕。
10 参考资料 & 致谢
-
「Q & A」不可食用的问答贴 by Tiw.
-
IOI2021 集训队论文 - 信息学竞赛中构造题的常用解题方法 by jiangly.
-
Tiw_Air_OAO 的博客中的构造专题 by Tiw.
-
感谢 Tiw 回答我在问答帖中的问题,我看见 Tiw 翻了包括 jiangly 论文在内的很多资料,真的好细心 www。
-
再次感谢 Tiw 向我推荐了那么多构造神题,本兔最喜欢她了 w(?
-
感谢 OneInDark 提供的长期精神支持。
为什么致谢?一个次要原因是算上文档末尾的保留空行后,markdown 源码恰好 712 行 awa!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步