AtCoder Beginner Contest 256 Solution

更好的阅读体验戳此进入

题面链接

题面 Luogu 链接

ab 跳了

cd 简单说一下做法,比较水就不实现了

[ABC256C] Filling 3x3 array

Solution

9 个未知量,6 个等式,存在一组线性相关的等式,这玩意实际上就是个简化版的高斯消元,所以考虑枚举四个数即可。复杂度 O(v4)

[ABC256D] Union of Interval

Solution

最开始想着套个 bitset 水过去,不过似乎比较难搞。随便搞个 ODT 或者 线段树 就能水过去,或者左端点排个序讨论一下也行。

[ABC256E] Takahashi's Anguish

题面

存在 n 个人,你需要确定一个序列 Pn 表示这 n 个人的排列,对于每个人,第 i 个人有且仅有一个 xi,表示不喜欢 xi 站在 i 的前面,若 xi 站在 i 的前面则会产生 ci 的不愉悦值,你需要确定排列以最小化不愉悦值之和,求最小值。

Solution

这道题想到方法之后就很简单了,是一道图论建模题。考虑将不喜欢的关系抽象成一条有向边,不难发现每个点都有且仅有一条出边,则最后形成的图在形态上一定类似一个基环树森林,或者说在形成的图中每一个连通块内最多含有一个环。

不难想到对于无环的一定有合法方案使得每一个点都不会有不喜欢的在其之前,对于环上的则一定至少需要破坏一条环上的边。于是我们跑一个类似拓朴排序的东西即可,对于无环的直接丢弃,有环的在环上找到边权最小的一条边保留即可。

Code

[ABC256F] Cumulative Cumulative Cumulative Sum

题面

给定序列 An,定义 Bi=j=1jAj,Ci=j=1iBj,Di=j=1iCj。存在两种操作:

1 x vAxv

2 x:查询 Dxmod998244353

Solution

就是维护一个高维前缀和,也是经典套路,无脑推式子:

Aa1a2a3ai
Ba1a1+a2a1+a2+a3a1+a2++ai
Ca12a1+a23a1+2a2+a3ia1+(i1)a2++ai
Da13a1+a26a1+3a2+a3(1+2++i)a1+(1+2++(i1))a2++ai

于是可以将 Dx 通过等差数列求和表示为:

Dx=i=1x12(1+x(i1))×(x(i1))ai

然后我们可以考虑将 (1+x(i1))×(x(i1))ai 化简,似乎就是尽量将与询问有关的 x 和前缀和抽离开,这样才可以便于用数据结构维护。即:

(1+x(i1))×(x(i1))ai=(xi+2)×(xi+1)ai=(x2ix+2xix+i22i+xi+2)ai=(x22ix+3x+i23i+2)ai=(x2+3x+2)ai2x(iai)+(i23i)ai

此时我们发现,对于每次询问 x 已知,只需要用数据结构维护 aiiai(i23i)ai 的前缀和即可 O(logn) 查询,并且支持单点修改。记得查询之后乘个 2 的逆元。最终复杂度 O((n+q)logn),可以通过。

Code

[ABC256G] Black and White Stones

题面

你现在有一个正 n 边形 , 边长为 d。从顶点开始,你每个长度 1 放一个石子,白色或者黑色。换句话说,每条边上有 d+1 个石子,相邻两边公用一个石子。

请问有多少种方案,使得所有边上的白色石子数量相同。对 998244353 取模。

n1012d104。注意 n 的范围。

Solution

dp(i,0/1,0/1) 表示考虑前 i 条边,开头为白色或黑色,结尾为白色或黑色的方案数,可以额外令 G(0/1,0/1) 表示一条边时放石子的方案数,转移显然,枚举每条边黑色点数共有 [0,d+1],处理答案后求和,然后矩阵快速幂优化一下即可。较为简单,具体算法看代码即可。

Code

[ABC256Ex] I like Query Problem

题面

给定 n,q,和序列 an,给定 q 次操作,有三种:

1 L R x:对于 [L,R] 内的所有 i 进行 aiaix

2 L R y:区间推平 [L,R]y

3 L R:输出 i=LRai

Solution

显然势能线段树,好像不是很难写。大概就是在一般的线段树基础上维护一个区间数是否均相同的标记,然后区间整除的时候直接通过这个实现 lazytag,这个复杂度经过一系列分析总之最后就是 O(nlog2n),实现起来很容易,代码也很短,不过,这太不优雅了

有区间推平操作,不难想到 ODT,显然会被卡,于是想到一种优化:

考虑这个的时候首先要知道一点语法知识,即对于 set 它是不同于一般的线性结构如 basic_string 的,一般的线性结构当插入删除时若改变 capacity 的话指针就会失效,但当在 set 中插入或删除元素的时候,对于非被删数元素的迭代器、指针和引用等都是不会失效的,用 cppreference 的话来说就是:

No iterators or references are invalidated.

众所周知,我们一般通过对变量的 mutable 修饰以使其可以在 set 中被修改,且按照 l 排序,所以这里我们可以将 r 也修饰为 mutable,这样的话我们便可以很方便的 O(1) 延申区间而不损失重构所需的 O(log),此时考虑,因为我们的 1 操作中 x2,所以显然对于一个数如果不推平为其它数的时候最多 log 次就会变为 0,所以我们在维护 1 操作时是可以同时将所有已经为 0 的数合并,这样可以有效减少块数,此时似乎对于 12 操作复杂度就已经正确了,不过如果我在每 log 次之后就重新推平满,这样复杂度似乎大概可能会被卡??不是很确定。

然后这样交上去会获得 28/30 的好成绩,不过依然会 T,也不难想到,我们直接拉满查询这个东西就退化成 O(nq) 的了。对于这个我最初的思路是,每当修改后再遇到一个 3 操作就重新建立一棵线段树维护所有数的值,这样在这次查询接着的所有查询就都是 O(logn) 的了,这样应该可以有效的水过一些构造的数据,当然卡的方法也很简单,只要在每次查询之间增加一次微小扰动的修改,但是这样每次都会重新建整棵树,最后复杂度还会退化为 12 常数的 O(nq)但是,经过下载数据点发现,T 掉的两个数据并没有如此设计,而是完全询问拉满,也就是说理论上这样实现之后是可以通过这道题的,估计出题人也没想到这种奇怪的做法。。。

但是我们可以尝试扩展这种做法,不难想到刚才说的做法复杂度主要浪费在了修改时只修改了一部分,而不需要整棵树重建,所以我们可以尝试在这里优化一下。不难想到,对于一般的线段树,其是支持除了 1 操作外所有操作的快速实现的,而 ODT 又可以将区间整除转化为区间推平,于是我们便不难想到,同时维护一棵 ODT 和一棵线段树,对于 1 操作在 ODT 上通过刚才说的优化转换为若干个区间推平,然后将推平作用到线段树上,对于 2 操作则 ODT 和线段树同时进行,对于 3 的查询直接在线段树上跑即可。这个的复杂度我没太想出来,不过翻了一下题解区,发现一篇 Blog [ABC256Ex] I like Query Problem 题解 和我的做法几乎相同,唯一的区别就是其在 ODT 维护区间整除的时候是直接重构的 ODT。总体来讲这两种维护方式差别是不大的,无非就是一只小 log 的区别,大佬的 Blog 最后分析的复杂度为 O(nlog2n),所以我的这个可能也是这个复杂度??总之表现还不错,最终 1.6s 跑完。

写在后面

这个奇怪的做法虽然能过,不过还是不推荐写,毕竟实现起来比较复杂细节较多,如果写的话似乎直接写重构部分 ODT 的方法会更好实现一些。一般的线段树写法实现大概也就 60 行,然后这个东西我实现大概写了将近两百行。。。

Code

UPD

update-2022_12_08 初稿

update-2022_12_08 修复 Ex 的一点小锅