杂题20201201

尝试把之前写的东西丢上来……


AGC018B Sports Festival

(N) 个人,每个人需要恰好参加 (M) 个项目之一,你可以决定每个项目的举办与否

给定 (N imes M) 的矩阵 (A_{i, j}) ,保证 (A_{i, 1}, A_{i, 2}, cdots, A_{i, M}) 是一个 (1, 2, cdots, M) 的排列。若项目 (A_{i, p})(A_{i, q}) 都要举办,且 (p<q) ,那么第 (i) 个人会选择参加项目 (p) 而不是项目 (q)

找到一种项目举办的方案,使得参加人数最多的项目参加人数最少

(N, Mleq300)

先假设所有项目都举办了,假设参加人数最多的项目 (p)(k) 个人参加

那么答案如果要小于 (k) ,项目 (p) 必定不能举办,递归下去做即可


AGC018C Coins

(N=X+Y+Z) 个三元组 ((A_i, B_i, C_i)) ,将其分成大小分别为 (X, Y, Z) 的三组 (S_1, S_2, S_3) ,最大化 (displaystylesum_{iin S_1}A_i+sum_{iin S_2}B_i+sum_{iin S_3}C_i)

(Nleq10^5)

先考虑 (Z=0) 时的做法,直接按照 (A_i-B_i) 排序即可,前 (X) 大的元素即为 (S_1) 中的元素

考虑先按照 (A_i-B_i) 降序排序,枚举 (S_1) 中排名最大的元素的下标 (x) ,那么前 (p) 个元素中我们会选恰好 (X) 个作为 (S_1) ,其余 (p-X) 个元素丢到 (S_3) ;后 (N-p) 个元素中我们会选恰好 (Y) 个作为 (S_2) ,其余 (N-p-Y) 个元素丢到 (S_3)

对于前 (p) 个元素,我们将其按照 (A_i-C_i) 排序,选前 (X) 个作为 (S_1) ,剩下的丢到 (S_3)

对于后 (N-p) 个元素,我们将其按照 (B_i-C_i) 排序,选前 (Y) 个作为 (S_2) ,剩下的丢到 (S_3)

用数据结构维护一下即可,时间复杂度 (O(nlog n))


一道题

给定一张 (n) 个点 (m) 条边的有向图,每条边有长度和 '('')' 之一的字符

(q) 次询问,每次给定 (S, T) ,问 (S)(T) 的一条满足依次经过的边上的字符连接起来形成了一个合法括号串的最短路或是判断无解

  • (nleq400)
  • (mleq5 imes10^4)
  • (qleq10^5)

yijan 友情提供了一份题解

我们考虑如何算出 ((u o v)) 的合法最短路。由于边权是非负的,可以用类 dijkstra 求解。

具体来说,考虑 ((u o v,w)) 在堆里面,表示 (u o v) 有合法路径长为 (w) 。然后更新有几种:

- 可以通过往 (u o v) 后面拼一条路径更新。也就是 (u o v, v o k) 两个合法路径合并,仍然是合法的路径,或者可以往前面拼一个路径更新,也就是 (k o u , u o v) 这样。这两种分别只需要枚举一下 (k) 更新即可。这是一个类似 Floyd 的转移方法,由于 dijkstra 取出的一定是当前的最短路,所以这样转移是对的。
- 可以通过 (p o u o v o q) 转移,其中 (p o u) 有一个 ( 的边,(v o q) 有一个 ) 的边。也就是往当前的合法括号序两侧加上一对括号。转移就是枚举一下这两个边即可。

场上就写的这个东西。考虑复杂度分析。点的个数是 (O(n^2)) 的,但是边的个数很多。边的个数大概是 (O(m^2 + n^3)) 的,第一种情况对于每个路径都有 (O(n)) 个转移,第二种情况,考虑每一对 () 边,不难发现它们一定能且仅能被转移一次,所以复杂度是 (O((m^2 +n^3) log (m^2 + n^3))) 的。

看起来很慢,但是会发现实际上 (m^2)(frac 1 4) 的常数。所以实际上可以跑过 (80) 分。

考虑如何去优化这个东西。

首先,第二种情况中同时枚举 (p,q) 非常不优秀。可以发现这个东西可以类似很多状压题的优化。也就是设一个 (g(u,v)) 表示 (u o v) 的最短路径,满足整个路径开头是左括号,且匹配完后剩下的一定是开头的左括号。

发现转移就稍微有所改变,对于每个 ((u o v,w)) 我们枚举一个 (p o u) 满足这条边上是一个左括号,然后转移到 (g(p,v)) ,并且把 (g) 同样加入优先队列中。如果队首是 (g) ,再用 (g(u,v)) 转移到 ((u o q)) 。两次转移都只需要枚举一次出边即可。于是边的个数被优化到了 (O(nm + n^3)) ,因为对于一个 (u) 枚举所有的 (v) 都需要考虑一次所有的 (u) 的入边。

但是这样的复杂度仍然是 (O((nm + n^3)log(nm + n^3))) ,还是不太能过。

考虑一个经典的 dijkstra 优化。可以通过斐波那契堆来优化。斐波那契堆是一种支持 (O(1)) 进行插入,查最大值,更改某个东西的值,合并,且 (O(log n)) 删除最大值的数据结构。

如果我们提前把 (O(n^2)) 个点给插入好,然后每次 push 操作都直接用修改来实现。不难发现这样队列里的元素不会超过开始的点数 ,于是删除最大值只需要进行 (O(n^2)) 次。换句话说,如果图的点数是 (O(n)) 边数 (O(m)) ,那么斐波那契堆来优化 dijkstra 后复杂度就是 (O(nlog n + m))

于是最后复杂度就变成了 (O(nm + n^3 + n^2log(n^2))) 。可以通过。

考虑一种更阳间的优化方法(虽然貌似有点卡常)。我们可以做值域分块。这样就可以做到每次修改最小值 (O(1)) ,删除一个值 (O(sqrt{n^2}))

这样做复杂度是 (O(nm + n^3)) ,常数很大。


CF316D3

有一个初始时 (p_i=i)(n) 的排列,你每次能交换排列中的两个位置上的数,同时每个位置有一个交换次数上限 (a_i) ,问能够得到多少种不同的排列,输出答案 (mod10^9+7) 的值

(nleq10^6; a_i=1, 2)

对于一次交换操作 ((i, j)) ,相当于合并两个环 / 将一个环拆成两份

考虑如何判断能否得到一个给定的排列,其合法当且仅当每个环中交换次数上限为 (1) 的点数不超过 (2)


CF477D Dreamoon and Binary

有一个给定的二进制数 (x) 和一个初值为 (0) 的二进制数 (n)

你每次可以进行以下两种操作之一:

  1. (n) 以二进制从高位往低位输出,不包含前导 (0)
  2. (n)(1)

你想要使得最终你的输出结果恰好时 (x) 从高位往低位的二进制表示,求出你最少需要多少次操作以及在保证操作次数最小的前提下有多少种不同的方案有多少种不同的方案(不要求最短)。答案 (mod10^9+7) 输出

(1leq x<2^{5000})

(g_{i, j}) 为考虑了 (x) 的前 (i) 位,这一步输出了 (x)([j, i]) 区间的取值,至少需要多少次操作 (1)

(f_{i, j}) 为考虑了 (x) 的前 (i) 位,这一步输出了 (x)([j, i]) 区间的取值,最小化操作 (1) 次数的前提下的方案数

统计答案时考虑 (f_{n, i}) 的贡献,确定了 (i) 的取值后,操作 (2) 的次数是固定的,我们只需要判断操作 (2) 的次数 (+g_{n, i}) 能否更新答案的最小操作次数。注意到 (5000<2^{13}) ,我们特殊处理一下 (n-ileq13) 部分即可

有转移 (f_{i, j}=displaystylesum_{[P(k)=1]}f_{j-1, k}) ,其中 (P(k))(1) 当且仅当: (x) 在区间 ([k, j-1]) 的取值没有前导 (0) 且不大于在区间 ([j, i]) 的取值; (k) 是满足上述条件的所有位置中 (g_{j-1, k}) 最小的之一

好像读错题了,但本质相同

然后大力前缀和一下即可,可能空间有点紧


CF468D Tree

给定一棵 (n) 个点的带边权树,求一个字典序最小的排列满足 (displaystylesum_{i=1}^ndis(i, p_i)) 最大

(1leq n, wleq10^5)

对于一条边 ((u, v, w)) ,它对答案的贡献次数的上界是 将这条边删掉后形成的两棵子树的大小的最小值

一种考虑方式是 (dis(i, p_i)=dep(i)+dep(p_i)-2 imes dep(lca(i, p_i)))(dep_i)(i) 到某个根的边权和),而 (dep(i), dep(p_i)) 之和是确定的,我们只需要最小化 (dep(lca(i, p_i))) 。而取重心为根,根的任意一个儿子子树大小不超过 (frac n2) ,因此必然存在一个排列,使得 (lca(i, p_i)=rt)

这样每条边的贡献都达到了上界

我们给 (rt) 的每个子树中的点标记一种颜色, (rt) 单独标记一种颜色

我们将找字典序最小的排列,看作一个二分图匹配,左部点是 (i) ,右部点是 (p_i) ,而一个左部点会向所有与其颜色不同的右部点连边

大概用 Hull 定理搞一下可以得到,这个匹配有解当且仅当 (2 imes maxleq|S|) ,其中 (max) 是左部点中最大的满足颜色相同的点集大小, (|S|) 是右部点剩余点的数量

接下来我们要按下标升序填 (p_i) ,每次填完后需要判断一下是否合法,用数据结构维护一下即可,有较为优秀的实现方法


CF1438E Yurii Can Do Everything

给定一个长为 (n) 的序列 (a_i) ,求有多少个数对 ((l, r)) 满足:

  • (l+1leq r-1)
  • (a_loplus a_r=a_{l+1}+a_{l+2}+cdots a_{r-1})

(nleq2 imes10^5; a_iin[1, 2^{30}))

我们记 (s_k=displaystylesum_{i=1}^ka_i)

那么合法序列有 (s_{r-1}-s_l=a_loplus a_rleq a_l+a_r)

结论:满足 (s_{r-1}-s_lleq a_l+a_r) 的数对数不超过 (O(nlog a_i))

大概是对于一个 (l) ,从左往右考虑每当出现了一个合法的 (r) ,前缀和大概会翻倍的样子

一种卡满的构造是,每三十个数放一组 (1, 2, 4, 8cdots, 2^{29}) ,每一组组内会有 (O(30^2)) 次贡献

然后暴力枚举判断即可


CF452F Permutation

给定一个 (n) 的排列,问是否存在一个长为 (3) 的子序列是等差数列

(nleq3 imes10^5)

假设该下标三元组为 ((i, j, k)) ,它们满足 (i<j<k and p_i-p_j=p_j-p_k=d)

我们枚举这个 (d) ,有一个结论是如果答案存在则有一组三元组的 (d) 不大,可以 (O(nd)) 解决


scanf("%s",&str[i]) 读单个字符时,会将 str[i+1] 赋为占位符

原文地址:https://www.cnblogs.com/Juanzhang/p/14266049.html