CQOI 2022 游记

Day 0

下午旷课去试机,一切都还好,除了Dev貌似没法调试。虽然我也不用调试,但机房里有人要,所以不得不想办法。最后搞了两个方案,一个是一波玄学的删了再装(我也不懂怎么搞的),一个就是改用VScode(还有一个就是不调试)。不会VScode,所以就顺便找green orange学了,配置这东西确实麻烦(,但用起来也挺方便。

这两天意识到一堆东西没有复习,所以便不抱有太大的期望,反正NOIP已经考萎了。

晚上没有失眠,挺好。

Day 1

到yc后看了一下座位,坐在边角的愿望实现了(1号)。然后就是拍照啥的,没啥好说的。

进考场比较早,大概还有半个小时才开考,就打了点头(还是用的Dev)。

解码后看题,T1题目极长,大概浏览一下发现是之前说着玩的手写编译器,没想到省选考了。于是就估摸着模拟吧,如果复杂度不对再去想吧。于是就开始码。一开始没看到字符的区分,导致调了一会儿样例后才发现。大概45分钟后调完过三个样例。

然后直接看T2。感觉挺可做的,就去搞。但怎么搞都逃不掉复杂度里有个 V 。中途突然想到或许可以把每个 lr 掏出来离散化,一段一段处理,但推了一下式子,发现那个形式不太会直接做,感觉要用科技(。忘了过了多久,反正发现想了很久了,就决定先把已经想了的打上去。磕磕碰碰地想了些细节,模拟了一下样例,114514分钟后才把第一问调过。感觉第二问好像再加个dp数组就可以了,于是过一会儿第二问也过了。但复杂度是 O(n2V) 的。有一个sub明显是 O(nV) 的,但有太多时间想了。

T3其实是和T2交错看的,T3题目也比较长,粗看题面以为也是个大模拟。但看到后面意识到只是套了层处理字符串的皮。自认为是一个有向图的路径覆盖问题,好像有环,但没管那么多,就直接敲了个Dinic上去。第二个样例的最后一个测试点过不了,发现有的匹配的权值是 2 ,于是又改成了费用流,过了所有样例的第一问。把输出方案的部分写上去后,发现某个样例过不了,结果是出现了选了环上的边的情况,这才意识到算法可能假了。但正好有个性质没用,或许是可以破环的,但也没多大时间想。却以为想出来个**的结论,去写了个Tarjan,要写玩的时候发现假的离谱(。

最后的时候发现T1有个奇怪的define替换成空字符,但没时间测试程序会不会萎了。undef的部分也没时间构造一点强数据测试。

中途我的键盘突然按不了了。重启后才好,大概耽搁了5分钟吧,还好影响不太大。鼠标有点硬,或许有一点点影响。

出考场后发现大家好像都只做了T1。pg说T2是拉插,突然感觉确实很像。然后他T2的暴力是 O(nV) 的,但没有写第二问。第三题大家好像都没什么思路。

下午看群的时候突然感觉自己T1的细节挂了。感觉又T又WA,于是睡个午觉,做的梦全是T1炸了。

醒了后决定复习一下dp,顺便把VScode下在自己电脑上,磕磕碰碰勉强装好了。于是就一直找dp题做。半个下午加半个晚上写了6道dp,然而感觉还是比省选的难度水一些。

晚上吃的串串,没啥好讲的。依旧是没有失眠。

Day 2

起得略晚,但还好。要出发的时候找不到身份证了,结果又被夹在了一个小本子里。

到考场比较晚,但也不算太晚,至少大家都还在排队。座位号变了,坐在16号。

解码看题。感觉T1应该可以做出来,T2是WC T1的变形?T3是某年NOIP T4的变形?

之后就比较痛苦,简单来说就是罚坐(。T1想了很久,各种方向想,各种性质找,都没有找到很好的方法。以为要反演,但不知道该怎么搞。最后还是先打了个暴力拿点分(中途还是发现了一点性质,比如每个数最多贡献4个质数,但并没有实际用处)。

T2想往着WC的方向去想,先换成二叉树,但当时听的比较匆忙,没去细想那个构造。考场上临时推,发现怎么推第二种情况都没法转化。最后想到的也只能转化成多叉树,但一直怀疑,所以时间基本就浪费了。

T3先打了暴力。想了个按权值从小到大考虑的贪心,但感觉很不对劲,一堆细节没解决,以及正确性无法证明。

之后T1又打了点其他部分分的暴力,调了一段时间才过样例。

那段时间大概又想了个要用FMT的东西,但不熟,忘了怎么实现,复杂度也有点问题。

整场考试,我一点半方向有一个人一直在哼歌,有很大影响!

出考场,green orange爆切两道。其他人好像和我一样。lzy的T1用了状压,然而我的那部分是暴力枚举子集,现在想来可能会T。

总之是萎了。自己太菜。一堆知识点不熟,一堆题没见过。还需拼命整。

对了,周末作业还没做(。

Day 6

成绩大概前两天就出来了,我差不多也早就知道考砸了。至于多砸,Day1没上100,Day2爆零(虽然本地和虚拟机上都有分)。这些已经过去,最重要的是接下来这一年。大概现在最需要的是一个正确的方向。

附录

题解

Day 1

T1

思路很简单,模拟就好了。

主要是细节问题:

  • 注意读题:有标识符这个东西,所以不要理解成了把 name 替换成一堆单词,中间用空格隔开(或许只有我这个**才会这么读题)。
  • content 有可能是空串,所以有的写法需要注意一下(比如我的写法就需要在define字符串连出去的字符串后面再连一个空串防止不展开)

其他也没啥好说的了,按照题目模拟就好了。

看似复杂度有点问题(我甚至用了 vector<string> 以及每次展开的时候都访问 map<string,int>) ,但还是能过,复杂度卡不满,数据也比较水。

T2

其实并不太难,考场上就观察到答案大概是个 O(n) 次多项式。

从暴力开始思考(下面记 V 为值域):

最暴力的暴力:O(Vn) ,加点搜索的优化就差不多是 O(ans1)

题目给了极差,那么如果枚举最小值的话,最大值的范围就固定了,那么每个数的值域选择范围就确定了,那么对于一条路径,直接把所有点的值域大小乘起来就好了?显然不对,因为随便选的话选出来的最小值可能会大于枚举的。但容易发现,其实稍微容斥一下就好了。形式化地:计算一条路径 (u,v) 的第一问时, f(u,v,l,r) 为其答案,而 g(u,v,l,r) 是通过直接把值域乘积来算出来的答案,那么这条路径的答案其实就是 g(u,v,l,r)g(u,v,l+1,r)

所以容易想到一个稍微优化了一点的暴力(也就是我考场上的写法):枚举一个端点,然后一边DFS一边算答案,每到一个点就枚举一下最小值。这样就可以 O(n2V) 解决第一问(注意长度大于 2 的路径会算两次)。而对于第二问,其实类似,转移的时候把第一问的方案和当前点对权值和的贡献乘起来再加一加就好了,应该比较简单。同样是 O(n2V)

有一档部分分显然给的是 O(nV) 。考虑枚举最小值,思考当 x,y 固定时,(u,v)f(u,v,x,y) 怎么算((u,v) 枚举的不同的路径) 。考虑树形dp:记 auu 的可选值域,大概长这个样子:max(min(ru,y)max(lu,x)+1,0)Fu 为以 u 为一个端点,另一个端点在 u 子树内的所有路径的 f 和,Gu 为两个端点都在 u 子树内且经过点 u 的路径的 f 的和。转移就比较好转移:

Fu=auvson(u)Fv

Gu=au(v,wson(u),vwFvFw+vson(u)Fv)

其中,Gu 的转移可以轻松的前缀和优化。 ans1=uGu 。这样就可以 O(nV) 地解决第一问。

对于第二问,发现不太好直接做,但从贡献的角度出发,记 buu 的可选值的和,那么 ans2=uGubuau ,这里的 Gu 和上面的略有不同,它是以 u 为根时,计算出来的 Gu ,应该比较好理解,问题是怎么算。考虑换根dp。当把根从 u 换到 v 时:

GvFv(FuFvau)

Fvav(FuFvau)

其实比较好好理解,就是都是算上 v 父亲上面那坨变成它的子树后的贡献,就是两个式子共有的 FuFvau (画个图就很好理解)。

所以就可以 O(nV) 地解决第二问?并不是,别忘了算答案的时候要算逆元,所以是 O(nVlogV) ,显然卡不过去,那么就要考虑把 GF 的含义稍微变一下,也就是在一开始算的时候就不要把 u 自己的值乘上去。转移方法类似,但要稍微复杂一点,有些边界问题需要斟酌(如 au=0 的时候) 。但这样做就是严格 O(nV) 的。然而!或许是我常数太大了,卡常后极限数据还是要跑 2.2s 左右......

对于正解,肯定是考虑把复杂度里的 V 去掉。很自然的想法是考虑枚举最小值时的特殊值(也就是我考场上想到的东西),就是 lu,ru,luK,ruK 。因为当最小值不断增加的时候每个点 u 的可选值域是一个分段函数,拐点就是上面这四个值。如果能把这 4n 个值掏出来排个序,去个重,在相邻两个拐点出快速计算出答案,那么问题就解决了。所以下面考虑值域在相邻两个拐点 α,β 之间时(半开区间,也就是 [α,β)),如何快速计算答案。

先考虑 F ,由于是相邻两个拐点之间计算答案,所以每个点的值域随最小值变化的函数要么是常函数,要么是一次函数,记为 Fu(α+x),x[0,βα),而观察到对于一条路径 (u,v) 来说,其答案其实就是 F(u,v)(α+x)=w(u,v)Fu(α+x) 。那么总的答案就是

(u,v)x=0βα1F(u,v)(α+x)

这是一个不超过 n+1 次的关于 x 的多项式函数(前缀和让这它升了一次)。所以可以拉格朗日插值。具体来说,βα1 不超过 n+1 ,那么直接用刚才的算法算出答案;否则,就用刚才的算法算出 x[0,n+1] 时的答案,然后前缀和一下跑拉格朗日插值,算出 βα1 处的答案。

对于第二问,就类似了,只不过每个点的贡献随最小值变化的函数要么是常函数,要么是二次函数,所以最后要插值的函数是一个不超过 n+2 次的多项式函数。

于是,对于每一个相邻拐点都跑一遍整个算法就可以遍历完整个值域,共有 O(n) 个拐点,跑一次最多进行 O(n) 次树形dp和换根dp以及一次拉格朗日差值,一次dp的复杂度为 O(n)。因为插的值是连续的,所以可以 O(nlogn) (O(n2) 也行,不影响整体复杂度) 。所以总的复杂度是 O(n3)

不是很好写,细节需要好好斟酌。

T3

建议想得特别清楚再写。

考场上其实把大体思路都想得差不多了,但是几个细节不会导致慌了,就连部分分都没拿到。

感觉有向图路径覆盖还是很好想的吧?把按顺序安排在一起可以产生贡献的两条信息连一条边,然后问题就变成了用尽量少的路径覆盖完这张有向图,把点拆成入点和出点,跑Dinic即可。

暴力建边的话边数是 O(M2) 级别的,所以要对每个人建虚点。

但是这样做有两个问题:

  • 形如 A B loushangB A louxia 的 两条信息安排在一起的话贡献是 2 ,于是就要跑费用流?跑费用流复杂度不太行。但可以发现,这样的两条信息强制安排在一起一定是不劣的,有多个角度的证明都说的通,这里就不赘述。强制安排后这两条信息是否就可以完全剔除网络流的建模中呢?显然不,因为它们可能还可以分别帮某些信息产生贡献,具体建模后面一起说。
  • 这张有向图是有环的!所以算法假了?幸好没有完全假,如果不输出方案的话,答案也是对的,因为题目中那个反复强调的性质:每个人都至少会发一条学术信息 。利用这个性质,可以破环,并保证答案不劣。

接下来叙述具体的建模及破环的方法:

建模:

需要 2n+2m+2 个点:源点 S ,汇点 T ,每个人两个点 x,x ,每条信息两个点 A,AxA 表示信息 A 的发出者,yA 表示信息 A 中涉及到的另一个人。

  • 对于所有信息 SAAT
  • 对于没有强制匹配的信息
    • 若是楼下型信息:yAA
    • 若是楼上型信息:AyA
    • 若是学术信息,忽略
  • 对于没有强制匹配或强制匹配了,自身是楼下型信息:AxA
  • 对于没有强制匹配或强制匹配了,自身是楼上型信息: xAA

可以画个图手推一下搞懂原理,自然就会怎么找方案了。

破环:

把原始方案从跑完Dinic的图(这张图由链和环组成)中拎出来后,用学术型信息破环。首先发现环上的信息一定要么都是楼上型信息,要么都是楼下型信息。于是强制匹配的信息也肯定不在环里。假设一条环长这样 ABCA ,设 AA 的学术型信息,它当前的连接方式是 PAQP 要么不存在,要么是楼上型信息, Q 要么不存在,要么是楼下型信息(由算法流程,可以通过归纳法证明)。

若该环全是楼上型信息,那么把连接方式换成:PABCAQ

若该环全是楼下型信息,那么把连接方式换成:PABCAQ

这样做之后所有学术型信息的 PQ 还是符合上面说的条件,所以可以重复利用(即可以随便找一个环上的点,通过其学术型信息来破环)。

细节:

太多了,难以列举。

卡常:

建议按上面的顺序建边,会快不少。空间尽量比好开。尽量减少递归和STL(比如手写Dinic里的队列)的使用。反正因人而异,我常数大,所以不得不想尽办法卡常(。

时间复杂度:O(MM)

花絮:建议别写 。反正我搞了很久,大概适合有较长时间闲着没事干的时候写。题目里面的楼上型信息和楼下型信息的定义很绕(比如A B loushang到底该读成 AB 的楼上,还是 A 的楼上是 B),以至于我现在还是晕的(语文不好,因为这玩意儿调了很久)。或许我上述的建模方式有问题,我没有仔细检查,因为实在是太绕了,可以看我代码来检验一下。

反正我认为是一道整体思路不难,细节恶心的网络瘤。

Day 2

T1

首先需要注意到几件事:

  • n 的范围没有什么用,因为值域很小,开个桶来存数。
  • 每个数的贡献只和它的质因子集合有关。
  • 一个数 m 的最大质数是 O(m) 的,并且超过 m 的质数最多只有一个。
  • 由上一条性质,除去 n 个数中每个数的最大质数,其他质数只有 14 个。
  • 一个数 m 最多有 4 个互不相同的质因子(对此题没用)。

然后就可以做了。

先考虑暴力吧: 单次查询 O(2min(n,s)) 枚举,再随便用个东西判断一下,可以得到 25 分。

然后容易想到容斥:先粗糙地用 (2x1)2nx 表示答案,其中 x 表示包含至少一个查询的某个质因子的数的个数,前面的表示包含的话就至少选一个,后面的表示不包含就不影响,随便选。显然,这样算会把不包含某些质因子的方案算上。所以考虑枚举哪些质因子没被包含,设 xp 表示除了 p 以外,包含至少一个其他查询质数的数的个数,那么答案就要减去 (2xp1)2nxp 。但是,这样有可能同时把不包含两个质数 p1,p2 的方案减两次,于是又要加......

其实上面一大段话都是在模拟容斥的过程,比较熟悉容斥的话其实就直接一个式子解决:

xS 表示除了集合 S 里的质数外,包含至少一个其他查询质数的数的个数,于是答案等于:

S(1)|S|(2xS1)2nxS

但这算法目前不能解决其他sub,这时候就可以利用性质了:第三条性质告诉我们,每个数最多有一个大于 43 的质因子,那么说,其实这些质因子可以不用容斥。因为如果把有大于 43 的质因子(后面简记为大质因子)的数按大质因子分类,分别分配到集合 Sp 里,那么容斥的时候就只用单独考虑查询中涉及所有的大质因子的集合 {Sp},只要保证每次容斥的时候从每一个 Sp 里都至少选了一个数,那么就一定包含查询中的大质因子。因此只需要对前 14 个质数容斥。

但现在还有一个问题是如何快速的在每个集合 Sp 中查询不包含集合质数 T 的数的个数(对应上面的 xS)。很简单,直接高维前缀和一下就好了。这样就可以做到单个集合预处理 O(slogs2slogs) ( slogs 其实就是 14 ),查询 O(1)

所以总的复杂度就分成预处理和查询两部分,长这样 O(n+s32log2s2slogs+2slogsci) ,看似跑不太过,但其实查询的时候跑不满,所以能过。

注意点细节上的问题:

  • 2 的幂次预处理,省掉一个log
  • 不包含大质数的数不能简单的单独开一个集合表示,大概要用 n 来减包含的(或许只有我这个**才会这么干)。
T2

先把括号序列转化成树(或许只有我这个**在考场上忘了怎么转化,手推一堆构造),把一对括号当成点,类似这种(()) 的关系就是父子关系,类似()() 的关系就是兄弟关系。

操作1是对于同一个点 u ,选择两个相邻的儿子 a,b ,把 b 以及它的子树都全部接到 a 上(之后记对 a 的操作为依靠,对 b 的操作为裂解),花费 xva+yvb ;操作2相当于是随意交换同一个点的子树,也就是不考虑子树顺序,那么操作1就可以随意选儿子。最后显然是要搞成(((((...))))) ,也就是一条链。

注意到应该从浅到深把点往下放最优,因为这样做的操作空间更大;然后对于每一层,其实都是想找一个点留在当前层,把其他点放到下一层,所以对于每个点,我们只关心它的深度,不关心它的具体位置。

x,y 的取值范围很小,考虑分类讨论。

  • x=0,y=0 输出 0 ,可惜没分。
  • x=0,y=1 ,只有裂解的点有代价,那显然每一层留下当前层权值最大的点不裂解,留在当前层即可。
  • x=1,y=1 ,依靠和裂解都有代价,那也很好想,对于每一层,前几次都依靠当前层权值最小的点(不要裂解权值最大的点),但是最后一次操作依靠权值最大的点,也就是把权值最大的点留在当前层。正确性的话比较显然吧。
  • x=1,y=0 ,只有依靠的点有代价。考虑把答案拆成两个部分:一个是每一层的最后一次操作的总代价,也就是把点固定在某一层的代价;一个是把点往下放的代价。然后要观察到一件事情,就是每层操作的点数随深度的增加,大概长这样 1,1,,1,2,2,2,,4,3,2,1 (类似凸的)。证明比较简单,在达到原树的最大深度之前,每层增加的点数至少为 1 ,而每层都会留下一个点,也就是减少一个点,那么总的点数就不减。达到最大深度后深度就显然单减了。然后对于前面的一段 1 ,显然是不用被操作的,就不管(之后的讨论都没管);对于 1 后面的 2 ,这是问题所在,先留个悬念;对于 2 之后的部分,每一层(最后两层无所谓,不用考虑)都可以把当前的最大值和最小值放下去。那么,如果没有 1 后面的那段 2 ,那么所有点中的最大值一定可以放到最下面的那一层去,而不产生第一类代价。这样显然最优,因为只有一个点可以不产生第一类代价(别忘了已经忽略了前面的一段 1 )。而对于第二类代价,都是由当前层的最小值贡献的,所以也更优。所以总结一下,如果没有 1 后面的 2 ,那么对于每一层,除了最后一次操作依靠最大权值,裂解最小权值以外,其他都依靠最小权值,裂解非最大权值。现在考虑如果有怎么办。首先要观察到从那一段 2 出来的点只有一个,然后结论是这个点要么是这些点里面权值最大的,要么是权值最小的。如何证明?很简单:首先,观察到总代价其实只跟当前层的最大值和最小值有关;然后现在想从那一段 2 里选出一个点来,那么它要么只能对最大值的那一部分产生贡献,要么只能对最小值的那一部分产生贡献,要么没贡献;于是,肯定最大值或最小值最优。于是就做完了(。

关于复杂度,四种情况分别为 O(1),O(nlogn),O(nlogn),O(n) (中间两类用multiset维护最值,最后一类直接前缀最值就可以了)。

T3

很神仙的树形dp了吧,状态设计很妙,转移也很妙,以下大部分内容基本来源于 这篇博客 (写得很好orz),对于一些内容加以完善,建议看式子的时候边画图便理解。

Sr 表示以 r 为根的子树, deguu 的儿子个数 (degu[0,2])depuu 的深度。认为点权是在树上移动的。

fu,v 表示 Slca(u,,v) 中,将 du 移出,移入的点权最终停留在 v ,删除完这棵子树的边以及 lca(u,v) 的出边的最小总代价(不算移入的点权的代价)

r=lca(u,v) 自下而上转移。

degr 分类讨论:

  • degr=0 :叶子节点,根据定义,把该节点的出边砍掉,代价只算该节点,即 fr,r=dr

  • degr=1

    dr 的孩子。

    • u=rfu,v=minw{du+fw,v}(lca(v,w)=d) ,意思是先砍掉 u 的出边,代价为 du ,然后把 dw 移到 d ,再砍掉 (u,d) (至于 lca(v,w)=d ,想想就知道为啥了,下面的形如这样的条件的解释类似)。枚举 v,w ,类似树上背包的分析,每对点只会在其 lca 处转移一次,时间复杂度 O(n2)
    • v=rfu,v=minw{fu,w+du+dv(depwdepv)}(lca(v,w)=d) ,意思是先把 du 移出 Sr ,增加 du 的代价,此时 dvd 处,再把 dv 移到 w 处,之前没有算 dv 的代价,所以要加上 dv 乘其操作次数 depwdepv 。枚举 u,w ,时间复杂度 O(n2)
  • degr=2

    • u=r :记 drv 一侧的孩子, d 为另一侧的孩子。则删的顺序为 r 的入边、d 的入边、d 的入边。枚举 w 使得 dwSd 移到 Sd ,枚举 x 使得 dxSd 移到 r ,枚举 dw 移动到的点 y 。则有:

      fu,v=minw,x,y{du+fw,v+fx,y+dw(depydepu)}=du+minw{fw,v+miny{dw(depydepu)+minx{fx,y}}}(lca(v,w)=d,lca(x,y)=d)

      第三层 min 只和 x,y 有关,枚举 x,y 预处理。第二层 min 只和 w,y 有关,枚举 w,y 预处理。最外层 min 只和 w,v 有关,枚举 w,v 计算。时间复杂度 O(n2)

    • v=r :记 dru 一侧的孩子, d 为另一侧的孩子。则删的顺序为d 的入边、d 的入边 、r 的入边。枚举 w 使得 dwSd 移到 Sd ,枚举 dr 移到的点 x ,枚举 dw 移到的点 y 。则有:

      fu,v=minw,x,y{fw,x+dv(depxdepv)+fu,v+dw(depydepv)+du}=du+miny{fu,y+minw{dw(depydepv)+minx{fw,x+dv(depxdepv)}}}(lca(w,x)=d,lca(v,y)=d)

      计算方法类似前一种情况,两遍预处理之后再计算。时间复杂度 O(n2)

    • ur,vr :记 dru 一侧的孩子, drv 一侧的孩子。则删的顺序为 d 的入边、r 的入边、d 的入边。枚举 dr 移到的点 w ,枚举 x 使得 dxSd 移到 r 。则有:

      fu,v=minw,x{fu,w+dr(depwdepr)+du+fx,v}=minw{fu,w+dr(depwdepr)+du}+minx{fx,v}(lca(u,w)=d,lca(v,x)=d)

      两个 min 里的东西类似之前的情况分别预处理,然后再计算。时间复杂度 O(n2)

答案为 minu{fu,1du} (根据定义,会多算一次 du 的代价)。

总复杂度为 O(n2)

posted @   Sword_K  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示