Processing math: 1%
Live2D

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 题目大意

  Private link.

题意简述   将 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 题目大意

  Private Link.

题意简述

  定义一个运算结点 u 有两个属性:当前容量 x_u、最大容量 V_u。提供以下单元操作:

  1. I 读入一个整数 x,令新结点 u=(x,x)

  2. F u 装满 u 结点,即令 x_u=V_u

  3. E u 清空 u 结点,即令 x_u=0

  4. C s 令新结点 u=(0,s)

  5. M u 令新结点 v=(0,x_u)

  6. T u v 不断令 x_u\leftarrow x_u-1,x_v\leftarrow x_v-1 直到 x_u=0x_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_vx_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,其中 ug 的某个孩子,并且 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 题目大意

  Private link.

题意概述   数轴上依次有 (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 题目大意

  Private link.

题意概述   你有 n 组硬币,第 i 组由上到下的价值依次是 a_i,b_i,a_i,只有取走上面的硬币才能取下面的。对于 k=1,2,\dots,3n,求总共取走恰好 k 个硬币时的最大价值和。n\le10^6

8.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 参考资料 & 致谢

  为什么致谢?一个次要原因是算上文档末尾的保留空行后,markdown 源码恰好 712 行 awa!

posted @   Rainybunny  阅读(1665)  评论(4编辑  收藏  举报
点击右上角即可分享
微信分享提示