CF杂题乱写

Codeforces Round #810 (Div. 2) D - Rain (差分)

更新了一下自己对于差分的认识。差分可以认为是一种离散的导数,也就是 $ \color{orange}{刻画变化率} $ 。

为了接受这个设定,我下面都把差分叫作导好了

我们考虑本题不消除任何操作的情况下的结果。其实是一些 $ \color{orange}{斜率为1或-1的离散的一次函数进行累加} $ 。斜率为1的话每个影响位置的导就为1。

同时我们知道:和的导也是导的和。那么多条的导也是可以累加的。累加的方式就是区间加。但由于我们只关注整个导函数最终的结果,我们可以通过差分的技巧来区间加(

得到的差分数组进行一次前缀和得到我们需要的导数组(也是差分数组),再进行一次前缀和就得到结果数组a。

再考虑如何处理消除操作。

这题我自推的一个结论是,积水最高点一定不在非降水点上。这个很显然(

那么对于一个位置 $ i $ ,一个位置 $ j $ 满足 $ a_j>m $ ,删除 $ i $ 可以使得 $ a_j \leq m $ :

$ a_j-max(0,p_i-|x_i-x_j|)\leq m $

$ a_j-p_i+x_j-x_i\leq m \ \ \ \ (x_i<x_j,x_j-x_i\geq p_i) $

$ a_j+x_j-m\leq p_i+x_i \ \ \ \ (x_i<x_j,x_j-x_i\geq p_i) $

$ a_j-x_j-m\leq p_i-x_i \ \ \ \ (x_i>x_j,x_i-x_j\geq p_i) $

我们需要 $ i $ 的影响范围覆盖了所有大于m的位置,并且 $ p_i-x_i $ 大于左侧的 $ a_j-x_j-m $ , $ p_i+x_i $ 大于右侧的 $ a_j+x_j-m $ ,取前后缀两值 $ RMQ $ 即可。


Codeforces Round #809 (Div. 2) D2 - Chopping Carrots (Hard Version)(二分,整除分块,初态固定技巧)

给定序列 $ a $ ,求构造出一个序列 $ p $ 使得 $ 1 \leq p_i\leq k $ ,使得下式取得最小值:

$ \max\limits_{1 \le i \le n}\left(\left \lfloor \frac{a_i}{p_i} \right \rfloor \right) - \min\limits_{1 \le i \le n}\left(\left \lfloor \frac{a_i}{p_i} \right \rfloor \right). $

求这个最小值(

求 $ max-min $ 问题的一个办法,可以 $ \color{orange}{枚举其中一端的值,二分另一端的值} $ 并 $ O(n) $ 判断可取性。时间复杂度 $ O(n^2logn) $ ,能过D1

再考虑另一个做法。

从构造数组 $ p $ 入手, $ \color{orange}{固定p的一个初始状态} $ :全为 $ 1 $ 。我们的 $ \color{orange}{操作就拆作了使p_i+1} $ 。然后再思考这种操作意味着什么:

a: 1 2 3 4
p: 1 1 1 1

如果我们使得 $ p_1 $ , $ p_2 $ 或者 $ p_3 $ 自增,答案只会更劣。所以我们应该做的是使当前的 $ \lfloor\frac{a_i}{p_i}\rfloor $ 最大的 $ p $ 自增。

可以用堆或者vector桶实现,时间复杂度 $ O(n^2logn) $ 或 $ O(n^2) $

考虑进一步优化。由于使得 $ p $ 自增,目的是减小 $ \lfloor\frac{a_i}{p_i}\rfloor $ ,可以从 $ \lfloor\frac{a_i}{p_i}\rfloor $ 的值入手。其取值只有 $ \sqrt a $ 种(此题 $ a $ 和 $ n $ 同阶),然后就可以优化到 $ O(n \sqrt n logn) $ 或 $ O(n\sqrt n) $ 了。xwx


Codeforces Round #814 (Div. 1) A2 / (Div. 2) D2 Burenka and Traditions(DP,操作拆分技巧)

给定一个数组,现在要执行若干个操作:

  • 选择一组 $ L \leq R $ 和一个整数 $ X $ ,令 $ L $ 到 $ R $ 的每一个元素都异或上 $ X $ ,代价为 $ \lfloor\frac{R-L+1}{2}\rfloor $ 。

求使得数组清零的最小代价。

这种对连续区间,代价下取整的操作,我们可以考虑 $ \color{orange}{把操作拆为更统一,简单的操作} $ 。

  1. 选择一个数,将其异或上某个数 $ X $ ,代价为 $ 1 $ 。
  2. 选择连续的两个数,将其异或上某个数 $ X $ ,代价为 $ 1 $ 。

答案的上限不会超过 $ n $ ,即一直使用第一个操作。

现在考虑如何优化。

例如

5 5

连续相同的数,可以一次消掉。

再如

7 4 7 2 6

111
100
111
010
110

像这样一段异或和为 $ 0 $ 的长度为 $ \text{len} $ 的区间,可以通过 $ \text{len-1} $ 的代价清除。

设 $ f_{i} $ 表示前 $ i $ 个位置清除的最小代价,依上原则可得一个 $ O(n^2) $ 的 $ \text{DP} $ 算法。

其中连续相同段可以 $ O(n) $ 预处理;异或和则满足很好的性质(两个位置有相同的异或前缀和,两位置之间异或和即为0),这一部分的转移又为 $ f_{i}=f_{j}+i-j $ ,那么用 $ \text{map / book} $ 记录最小的 $ f_{j}-j $ 即可。

得到这个 $ O(n)/O(nlogn) $ 的动态规划做法。


Codeforces Round #814 (Div. 1) B / (Div. 2) E. Fibonacci Strings(贪心)

给定一个数组 $ a $ ,表示第 $ i $ 种字符共有 $ a_i $ 个。

需要将这些字符排序,使得连续相同字符的个数组成一个斐波那契数列的某前 $ m $ 项。

问是否有合法方案。

考虑从后往前构造,每次取出最大的 $ a_i $ 这样一个贪心。

正确性的证明即:

$ a_k> a_{k-1}+a_{k-3}+\cdots $

即如果一个数大于等于当前需要构造的最大数 $ a_k $ ,那么它就必须去构造这个 $ a_k $ 。


Educational Codeforces Round 52 F. Up and Down the Tree(DP)

给定一棵树和一个整数 $ k $ ,起始位置在根节点,每次可以跳到该树的一个叶子上,然后向上最多跳 $ k $ 步,问最多可以跳到多少个叶子节点上。

研究一下这个操作。当从节点 $ x $ 开始往下跳,如果其子树中某个叶子 $ v $ 满足 $ dep_v-k>dep_x $ ,那么就可以白嫖 $ v $ 的价值然后跑回去了()

跑完所有满足条件的 $ v $ 之后,可能还要跳到一个跳不回 $ x $ 的叶子节点去。

设 $ f_x $ 表示以 $ x $ 为根的子树的最大价值, $ g_x $ 表示从 $ x $ 出发,并且最终回到 $ x $ 的最大价值。

$ g_x $ 就是这个子树中满足上述条件的 $ v $ 的个数。(?

至于 $ f_x $ ,在跑完 $ g_x $ 对应点的情况下,要选择一个子树入坑(?

由于 $ g $ 已经被跑完了,所以选择一个 $ f_y-g_y $ 最大的子树即可。


AIM Tech Round 3 (Div. 1) C. Centroids(DP,树的重心)

给定一棵树,可以删掉其中一条边再加上一条边使得它还是一棵树。

问经过这种操作,树上的各个节点能不能变成树的重心。

首先这是个无根树,稍稍研究一下,可以先将树的重心作为根。

这样做的好处在于:如果树上某个节点不是树的重心,可能需要断开的地方一定在该树的该节点为根的子树之外。 $ \color{orange}{把处理的方法统一起来!} $

背后的原理是 $ \color{orange}{以树的重心为根时,每个子树大小不会超过\lfloor\frac{n}{2}\rfloor} $ 。这个性质可以好好利用一下,也是淀粉质 $ nlogn $ 级别复杂度的关键所在。

接着就是怎么断了

其实就是在去掉该子树的这个树找到最大的大小不超过 $ \frac{n}{2} $ 的一个子树(

可能选择断掉的地方,一是除了某祖先子树之外的树的部分,另一种是某祖先的不包含自身所在子树的子树。于是:

$ f_y=max(f_x,n-siz_x,x的包含y子树外最大子树大小)(\leq \lfloor\frac{n}{2}\rfloor) $

最后判断 $ n-f_x-siz_x\leq\lfloor\frac{n}{2}\rfloor $ 是否成立就好了。


Codeforces Round #815 (Div. 2) D2. Xor-Subsequence (hard version)(DP,异或,01Trie)

给定一个长度为 $ n $ 的序列 $ a $ ,下标由 $ 0...n-1 $ 。对于一个符合要求的序列 $ b $ :

  1. $ 0<b_0<b_1<b_2<\cdots<b_{m-1}<n $

  2. $ a_{b_i} \oplus b_{i+1} < a_{b_{i+1}} \oplus b_i $

求符合要求的 $ b $ 的最大长度。

只涉及相邻二者,直接 $ \text{DP} $ 。把上面那个形式写好看一点:

$ f_i=max{f_j} \ \ \ \ \ a_j \oplus i < a_i \oplus j\ \ \ (j<i) $

先看 $ \text{D1} $ 。 $ n\leq3\times10^5 $ , $ a_i\leq200 $

$ \color{orange}{位运算的性质是不进位。} $ 考虑 $ i $ 去异或 $ a_j $ ,只会改变其后八位。

重新理解一下二进制下 $ \color{orange}{数的比较大小。从最高位开始起,找到两数不相同的第一个位置,比较这一位的大小。} $

由于 $ i $ 异或上 $ a_j $ , $ j $ 异或上 $ a_i $ 最多只改变后八位, $ j $ 又小于 $ i $ ,所以 $ j $ 和 $ i $ 的八位以上位必须相同。也就是最多相差 $ 255 $ 。

所以枚举 $ j \in [i-255,i-1] $ 。时间复杂度 $ O(n2^{\lceil log_2maxa \rceil}) $

看看 $ \text{D2} $ , $ a_i \leq 10^9 $ 。针对这个最长公共前缀继续思考。

考虑对于 $ i $ , $ a_i $ , $ j $ , $ a_j $ 保留这个前缀,则

\[i \oplus a_j = j \oplus a_i \]

\[\iff \]

\[i \oplus a_i = j \oplus a_j \]

除去这个公共前缀,已知 $ i $ , $ a_i $ 的下一位分别是 $ \text{B1} $ , $ \text{B2} $ ,那么满足题意的 $ j $ , $ a_j $ 的下一位分别是 $ \text{!B2} $ , $ \text{B1} $ 。

于是可以建个 $ \text{01Trie} $ ,存储 $ j \oplus a_j $ ,并且存储该位每组 $ (\text{B1},\text{B2}) $ 对应的 $ max{f_j} $ ,然后就能 $ \text{DP} $ 了。

Code
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(3e5+233)
int a[MAXN];
int maxa=0,cnt,Node=0;
struct qwq
{
	int s[2],dep;
	int p[2][2];
	qwq() { s[0]=s[1]=0; p[0][0]=p[0][1]=p[1][0]=p[1][1]=-1; }
}e[MAXN*31];
int f[MAXN];
inline int Bit(int A,int P) { return ((A>>(cnt-P))&1); }
int tot;
struct par{ int aa,jj; };
inline int New(int D)
{
	Node++;
	e[Node].s[0]=e[Node].s[1]=0;
	e[Node].p[0][0]=e[Node].p[0][1]=e[Node].p[1][0]=e[Node].p[1][1]=-1;
	e[Node].dep=D;
	return Node;
}
void upd(int cur,int I)
{
	int P=Bit(a[I]^I,e[cur].dep+1); int NP=P^1;
	if (e[cur].s[NP]) f[I]=max(f[I],e[e[cur].s[NP]].p[Bit(a[I],e[cur].dep+1)^1][Bit(I,e[cur].dep+1)]+1);
	if (!e[cur].s[P]) return;
	upd(e[cur].s[P],I);
}
void ins(int cur,int D,int A1,int A,int FA)
{
	if (D) { int P1=Bit(A,D),P2=Bit(A1,D); e[cur].p[P1][P2]=max(e[cur].p[P1][P2],FA); /*printf("----------%d\n",e[cur].p[P1][P2]);*/ }
	if (D==cnt) return;
	int P=Bit(A1^A,D+1);
	if (!e[cur].s[P]) e[cur].s[P]=New(D+1);
	ins(e[cur].s[P],D+1,A1,A,FA);
}

inline void R()
{
	int n;
	scanf("%d",&n); maxa=0; cnt=0; Node=0;
	for (int i=0;i<n;i++) scanf("%d",&a[i]),maxa=max(maxa,a[i]),f[i]=1;
	if (maxa==0) { puts("1"); return; }
	else cnt=((int)log2(max(maxa,n)))+1;
	New(0);
	int ans=0;
	for (int i=0;i<n;i++)
	{
//		printf("TURN %d :\n",i);
		if (i) upd(1,i);
		ins(1,0,a[i],i,f[i]);
//		if (i==201) printf("%d %d %d : %d\n",a[i],i,a[i]^i,f[i]);
		ans=max(ans,f[i]);
	}
//	printf("%d %d %d %d\n",f[1],f[2],Node,cnt);
//	printf(": "); for (int i=0;i<n;i++) if (f[i]!=1) { printf("%d:%d ",i,f[i]); break; } puts("");
	printf("%d\n",ans);
	return;
}

int main()
{
//	freopen("r.txt","r",stdin);
	int T;
	scanf("%d",&T);
	while (T--) R();
	return 0;
}


呜呜,踹树深度相关是 $ max{n,a_i} $ ,查了半天。


Codeforces Round #809 (Div. 2) E. Qpwoeirut and Vertices(并查集,线段树,kruskal重构树)

联通时间戳(By Hydrogen)

对于询问一个点对的联通时间戳,可以以时间戳为边权建 $ \text{MST} $ ,查询两点路径上的最大边权。俩 \(log\) 薄纱

或者离线,用到启发式合并的神奇复杂度。先把所有的询问用 \(vector\) 打进对应点,加边合并并查集的时候遍历较小的一个集合,爆更答案。

询问一个编号区间点的联通时间戳,只需要维护相邻两个点的连通情况,归到和上面相同的问题,将相邻两点连通最小时间戳扔到线段树上,所求即区间查询最大值。


Educational Codeforces Round 32 G. Xor-MST(异或,最小生成树,01trie)

异或先想01踹

根据 \(kruskal\) 的贪心步骤,我们每次需要找出最小的边来加入(?

不好处理。这种处理 \(n^2\) 组关系中最小的 \(k\) 组的形式,在这道题里并不好用。

考虑一下那个 \(Boruvka\) ,做法是每次在连通块里枚举出最小的出边然后连出去。

把整个踹树建出来,我们对踹的一个有分叉的节点,就把这俩子树合起来。启发式合并应该可以做,对于较小子树中的每个值,在较大子树里面跑跑最小答案得了(

posted @ 2022-08-11 12:42  Akuto_urusu  阅读(58)  评论(0)    收藏  举报