CF 刷题笔记 壹
多刷 CF,锻炼思维。
1987D(1800)
注意到 Alice 每一轮只会吃所有满足条件的蛋糕中最小的一个,并且只要她吃了同美味值的蛋糕中的一个,这个美味值的蛋糕就没用了,所以 Bob 想达到目的肯定要将同美味值的一组蛋糕全部吃掉。设 \(f_{i,j}\) 表示当前考虑到第 \(i\) 组蛋糕,当前 Bob 有 \(j\) 的空闲时间的最小贡献。设 \(c_i\) 表示美味值为 \(i\) 的有多少个。转移:\(f_{i,j}=\min\{f_{i+1,j+1}+1,f_{i+1,j-c_i}\}\),其中 \(j \geq c_i\)。时间复杂度 \(O(n^2)\)。好题。code
1981C(1800)
不妨设所有不等于 \(-1\) 的 \(a_i\) 组成序列 \(\{c_k\}\), \([1,c_1]\) 和 \([c_k,k]\) 我们只要不断操作就行了,而 \([c_i,c_{i+1}]\),只要注意到在一棵二叉树上,节点 \(i\) 的父节点编号正好是 \(\lfloor\frac{i}{2}\rfloor\),那么我们的问题就转化为求节点数为 \(V\) 的二叉树上 \(c_i\) 到 \(c_{i+1}\) 的长度为 \(x\) 的路径。复杂度 \(O(n\log V)\),实现上可能是 \(O(n\log^2 n)\) 的。好题!也可以考虑每一次除以二相当于二进制表达扣掉末尾一位,乘二相当于二进制表达末尾加上一位,然后将问题转化。code
1979D(1800)
可以知道满足 \(k-proper\) 的 01 串只可能是每 \(k\) 个数相等,相邻的两组 \(k\) 个数不相同。枚举 \(p\),用字符串哈希判断字符串相等即可。不如前面两个题好。code
1656H(3200)
画风突变。设 \(a_i=\prod\limits_{j=1}^{k_i} p_j^{\alpha_j}\),若 \(\alpha_i > \max\limits_{x \in S_B} \{\log_p x\}\),那么 \(a_i\) 就只能删掉了。定义 \(v_p x\) 表示 \(x\) 的质因数分解中 \(p\) 的指数。则该条件可表示为:
暴力判断显然过不了。考虑到我们的操作其实是求 \(\gcd\),单点删除。对于每个 \(a_i\) 和 \(b_i\) 维护一颗 seg,对于 \(a_i\) 的这棵 seg,每编号为 \(j\) 的叶节点维护 \(\frac{a_i}{\gcd(a_i,b_j)}\),单点删除就相当于把这个点的值改为 \(0\)。复杂度 \(O(n^2 \log n \log V)\),其中 \(\log V\) 是 \(\gcd\) 的复杂度,用 pbds 可能能把这个消掉。code
1994E(2000)
显然 \(k=1\) 时的答案就是这棵树的节点数。我们可以通过砍叶子结点的方式得到 \(1\) 到 \(siz\) 的所有数值。问题就变成了如何选数,也就是 \(ans\) 需要这个 \(siz\) 的哪几位做出贡献。一位一位考虑就可以了。只要想通前两句话,这个题就很好写。诈骗题。code
13E(2700)
弹 飞 绵 羊。code
616E(2200)
最主要的是整除分块的结论及其证明。
方便起见,我们设 \(f(x)=\lfloor\frac{n}{x}\rfloor\)。
对于右面这一坨整除分块。注意到 \(f(k)\) 的下降是阶梯状的,我们考虑对于每个阶梯的值算出来再乘上这一段区间内 \(k\) 的和就可以了。对于 \(i,j\),如果 \(f(i)=f(j)\),则 \(j\) 的最大值是 \(f(f(i))\)。
证明
设 $k=f(i)$,可知 $k \leq \frac{n}{i}$。可推出:故 \(j\) 取 \(\max\) 时 \(\forall i\) 满足条件,\(i=i_{\max}=f(k)=f(f(i))\)。\(\square\)
于是我们就可以在 \(O(\sqrt n)\) 的时间内完成求和。code
1045G(2200)
紫色的 2200。三维偏序问题的变形。将坐标离散化,搞出坐标左右端点在离散化结果上对应的区间,注意到 \(i,j\) 两个人能互相看到条件是(不妨设 \(x_i \geq x_j\)):
结合 (1)(2) 式,可得
可推出 \(r_i \leq r_j\)。
考虑将 \(r\) 作为三维偏序中的一维,将 IQ 值作为另一维,\(x\) 值作为第三维,我们以 \(r\) 排序,分治过程中对左右两端。这里顺便写一下 CDQ 分治的思想和方法。
CDQ 分治主要解决关于点对的问题,是离线做法。递归求解区间 \((l,r)\) 上包含的点对时,将 \((l,r)\) 拆成 \((l,mid)\) 和 \((mid+1,r)\) 两段,对于 \(i,j \in [l,mid]\),递归求解;对于 \(i,j \in (mid,r]\),递归求解;对于 \(i \in [l,mid],j \in (mid,r]\),单独设计算法求解。伪代码:
solve(l,r){
mid=(l+r)/2
solve(l,mid),solve(mid+1,r)
solve the third situation
}
1228E(2300)
容斥。先讲原理。对于 \(n\) 个集合 \(S_1,S_2,\dots,S_n\),我们有:
其中 \(a\) 是 \({x\in \mathbb Z|1\leq x\leq n}\) 的一个子集。可以通过 \(n=3\) 的情况来类比得到证明。
回到这个题。我们考虑每一行合法的情况数就是 \(k^n-k^{n-1}\)。正难则反,我们考虑让列不合法。情况数就是分别选 \(1\) 到 \(n\) 列,让他们不合法,选 \(i\) 列不合法的情况数是:
有因为每一列的情况是等价的,所以总情况数就只用取个 \(n\) 次方,乘上 \(n \choose i\),这就是容斥公式中的 \(\sum\limits_{a_i\le a_{i+1}}|\bigcap\limits_{i=1}^m S_{a_i}|\) 部分,然后我们套公式就可以得到答案是:
然后就可以了。代码很短。code
1787H(3300)
答案最优就是损失最少。由题意,我们有:
记 \(c_i=b_i-a_i\),则 \(Ans=\sum_{i=1}^n b_i -\sum_{i=1}^n\min\{k_i\cdot t,c_i\}\)。如果我们钦定一部分题目花费 \(c_i\) 的代价,则剩下的题目应当贪心地将 \(k_i\) 降序排列(排序不等式)。设 \(f_{i,j}\) 表示 \(i\) 个数中钦定 \(j\) 道题得 $k \cdot t $ 的最小代价,有转移:
时间复杂度是 \(O(n^2)\) 的,考虑在转移过程中优化。打个表发现 \(f_{i,j}\) 关于 \(j\) 是凸的(你问我怎么想到的,汪娟会告诉你,这就是你与 CF rating 3300 的区别)。于是我们设 \(g_{i,j}\) 表示 \(f_{i,j}-f_{i,j-1}\),然后用它改写 DP 式子得到:
我们发现上式的元太多了,于是我们将上式记为(1)式。然后用 \(j-1\) 代替 \(j\) 后改写上式,得:
将上式记作(2)式,用(1)式减(2)式,得:
\(k\) 是不增的。注意到 \(g_{i,j}-g_{i,j-1} \geq k_i\),接下来我们尝试证明这个性质。
对 $g_{i,j}-g_{i,j-1}\geq k_i$ 的证明
运用数学归纳法。
当 \(i=2\) 时,结论显然成立;
若 \(i=i\) 时成立,则,当 \(i=i+1\) 时,有:
观察 \(\min\{g_{i,j}+c_i,k_i \cdot j\}\) 这一项,发现 \(\Delta g_{i,j}\geq \Delta k_i \cdot j\),则存在一个 \(pos\) 使 \(\forall j \leq pos,g_{i,j}\leq k_i\cdot j,\forall j \geq pos,g_{i,j} \geq k_i \cdot j\),则由 \(g_{i-1}\) 变换到 \(g_i\) 的过程如下:\(j \le pos\) 的位置不变。\(j \ge pos\) 的位置的值加 \(k_i\) 。中间插入 \(k_i \cdot j - c_i\)。
于是我们只用考虑中间位置与其左右两边的差就可以了。
可证明
由此我们可以找到一个位置 \(pos\) 使得 \(\forall j \leq pos,g_{i,j}\leq k_i\cdot j,\forall j \geq pos,g_{i,j} \geq k_i \cdot j\)。于是从 \(g_{i-1}\) 推到 \(j_{i}\) 就变得简单了起来。我们用平衡树维护 \(g_i\),就可以把时间复杂度做到 \(O(n\log n)\)。这种在斜率上操作的方法叫 Slope Trick。code
1187E(2100)
换根 DP 好题。首先我们注意到最后的结果在我们选出第一个填黑色的节点的时候就已经确定了,于是我们设 \(g_i\) 表示 \(i\) 为第一个填黑色的节点时的答案,直接求 \(g_i\) 不是很方便,于是我们考虑设 \(f_i\) 表示选了 \(i\) 后其子树对答案的贡献。可得 \(f_i=siz_i+\sum_{j \in son\{i\}}f_j\),得到 \(g_1=n+\sum_{\{j,1\}\in E}f_j\),如果每次都求一遍复杂度为 \(O(n^2)\),考虑换根 DP。先求出 \(g_1\),然后考虑 \(g_i\) 的递推式,这玩意显然要自上向下推,设 \(i=fa_j\),易得(虽然我得来并不易) \(g_j=n+(n-siz_j)+\sum_{k \in son\{i\},k \neq j}f_k+\sum_{k \in son\{j\}}f_k\),稍微推一推,得 \(g_j=g_{i}-2siz_j+n\),然后就 \(O(n)\) 地过掉了。code
506D(2400)
先考虑暴力,每次枚举颜色,在查询的时候如果两点只用同一种边就能联通,那么这个颜色对答案贡献加一,维护联通用并查集,时间复杂度 \(O(qm\log n)\),在颜色数很小的时候可以过。再考虑另一种暴力,先对每一种颜色分别建图,然后枚举这上面的每个点对查询,如果联通则贡献加一,时间复杂度 \(O(mn^2\log n)\),在 \(n^2<q\) 的时候会比前一种情况优。
接下来考虑如何优化,这里使用根号分治。注意到如果当前颜色的边数小于 \(\sqrt m\) 的时候,这样所有这样的颜色的连通块的点的总数的平方和不超过 \(n\sqrt n\),于是可以用第二种暴力,时间复杂度 \(O(n\sqrt n \log n)\);如果当前颜色边数大于 \(\sqrt m\),那么满足这个条件的颜色数最多 \(\sqrt m\),故使用第一种暴力,时间复杂度 \(O(\sqrt m q\log n)\),加一块,稳稳过。code
809C(2600)
先把 \(a\) 的表打出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 1 4 3 6 5 8 7 10 9 12 11 14 13 16 15 18 17 20 19 22 21 24 23 26 25 28 27 30 29 32 31
3 4 1 2 7 8 5 6 11 12 9 10 15 16 13 14 19 20 17 18 23 24 21 22 27 28 25 26 31 32 29 30
4 3 2 1 8 7 6 5 12 11 10 9 16 15 14 13 20 19 18 17 24 23 22 21 28 27 26 25 32 31 30 29
5 6 7 8 1 2 3 4 13 14 15 16 9 10 11 12 21 22 23 24 17 18 19 20 29 30 31 32 25 26 27 28
6 5 8 7 2 1 4 3 14 13 16 15 10 9 12 11 22 21 24 23 18 17 20 19 30 29 32 31 26 25 28 27
7 8 5 6 3 4 1 2 15 16 13 14 11 12 9 10 23 24 21 22 19 20 17 18 31 32 29 30 27 28 25 26
8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 24 23 22 21 20 19 18 17 32 31 30 29 28 27 26 25
9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8 25 26 27 28 29 30 31 32 17 18 19 20 21 22 23 24
10 9 12 11 14 13 16 15 2 1 4 3 6 5 8 7 26 25 28 27 30 29 32 31 18 17 20 19 22 21 24 23
11 12 9 10 15 16 13 14 3 4 1 2 7 8 5 6 27 28 25 26 31 32 29 30 19 20 17 18 23 24 21 22
12 11 10 9 16 15 14 13 4 3 2 1 8 7 6 5 28 27 26 25 32 31 30 29 20 19 18 17 24 23 22 21
13 14 15 16 9 10 11 12 5 6 7 8 1 2 3 4 29 30 31 32 25 26 27 28 21 22 23 24 17 18 19 20
14 13 16 15 10 9 12 11 6 5 8 7 2 1 4 3 30 29 32 31 26 25 28 27 22 21 24 23 18 17 20 19
15 16 13 14 11 12 9 10 7 8 5 6 3 4 1 2 31 32 29 30 27 28 25 26 23 24 21 22 19 20 17 18
16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
18 17 20 19 22 21 24 23 26 25 28 27 30 29 32 31 2 1 4 3 6 5 8 7 10 9 12 11 14 13 16 15
19 20 17 18 23 24 21 22 27 28 25 26 31 32 29 30 3 4 1 2 7 8 5 6 11 12 9 10 15 16 13 14
20 19 18 17 24 23 22 21 28 27 26 25 32 31 30 29 4 3 2 1 8 7 6 5 12 11 10 9 16 15 14 13
21 22 23 24 17 18 19 20 29 30 31 32 25 26 27 28 5 6 7 8 1 2 3 4 13 14 15 16 9 10 11 12
22 21 24 23 18 17 20 19 30 29 32 31 26 25 28 27 6 5 8 7 2 1 4 3 14 13 16 15 10 9 12 11
23 24 21 22 19 20 17 18 31 32 29 30 27 28 25 26 7 8 5 6 3 4 1 2 15 16 13 14 11 12 9 10
24 23 22 21 20 19 18 17 32 31 30 29 28 27 26 25 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9
25 26 27 28 29 30 31 32 17 18 19 20 21 22 23 24 9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8
26 25 28 27 30 29 32 31 18 17 20 19 22 21 24 23 10 9 12 11 14 13 16 15 2 1 4 3 6 5 8 7
27 28 25 26 31 32 29 30 19 20 17 18 23 24 21 22 11 12 9 10 15 16 13 14 3 4 1 2 7 8 5 6
28 27 26 25 32 31 30 29 20 19 18 17 24 23 22 21 12 11 10 9 16 15 14 13 4 3 2 1 8 7 6 5
29 30 31 32 25 26 27 28 21 22 23 24 17 18 19 20 13 14 15 16 9 10 11 12 5 6 7 8 1 2 3 4
30 29 32 31 26 25 28 27 22 21 24 23 18 17 20 19 14 13 16 15 10 9 12 11 6 5 8 7 2 1 4 3
31 32 29 30 27 28 25 26 23 24 21 22 19 20 17 18 15 16 13 14 11 12 9 10 7 8 5 6 3 4 1 2
32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
打表程序:
int a[105][105],arr[205];
std::set<int> se;
int main(){
freopen("a.txt","w",stdout);int n=32,m=32;a[1][1]=1;
for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){
if(i==1&&j==1){continue;} se.clear();
for(int k=1;k<i;k++)se.insert(a[k][j]);
for(int k=1;k<j;k++)se.insert(a[i][k]);
int cnt=0; a[i][j]=-1;
for(auto x:se)arr[++cnt]=x;
if(arr[1]>1)a[i][j]=1;
else{for(int k=2;k<=cnt;k++){if(arr[k-1]+1!=arr[k]){a[i][j]=arr[k-1]+1;break;}}}
if(a[i][j]==-1)a[i][j]=arr[cnt]+1;
}
}for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i][j]<10)std::cout<<" "<<a[i][j]<<" ";
else std::cout<<a[i][j]<<" ";
}std::cout<<"\n";
}
}
然后发现对于每一个大小为 \(2^k\) 的正方形(下文简称“\(k\) 正方形”)区域都均分成了四块,每一行、每一列都填满了 \(1\) 到 \(2^k\) 的排列(性质 1),并且左上角和右下角一样,右上角和左下角一样,都是左上角对应位置的数字加上 \(2^{k-1}\)(性质 2)。
对于矩形 \((n,m)\) 的询问,我们找最小的一个可以包住它的 “\(k\) 正方形”。注意到 \(a\) 关于主对角线轴对称,所以不妨设 \(n\geq m\),这样讨论矩形在 \(k\) 正方形中的位置。
如果 \((n,m)\) 在右下角,我们把 \(k\) 正方形分成四个 \(k-1\) 正方形,然后根据性质 1,我们可以利用求和公式求出要求的矩阵中左上角、右上角、左下角的和,对于右下角我们递归求解。
如果 \((n,m)\) 在右上角,左边那一块可以用求和公式求出,右半边递归求解,注意要加上 \(2^{k-1}\) 的偏移量。
这样就可以 \(O(可过)\) 了。code
1891F(2000)
正 难 则 反。code
837D(2100)
先设 \(f_{i,j,x,y}\) 表示 \(i\) 个数,选了 \(j\) 个,其中 \(x\) 个 2,\(y\) 个 5,是否可行。注意到不可过,于是将 \(x\) 这较大的一位放到结果,这相当于是某些题里把较小的答案放到状态里的反过程。设 \(f_{i,j,x}\) 表示 \(i\) 个数,选了 \(j\) 个,其中 \(x\) 个 5的时候最多多少个 2,得到转移 \(f_{i,j,x} = \max\{f_{i-1,j-1,x-c5(i)},f_{i-1,j,x}\}\),压维,后两维倒叙就过了。code
1718A2(1900)
我们显然可以通过 \(n\) 次异或 \(a_i\) 来通过 \(n\) 的代价来把 \(a_i\) 全变成 \(0\),注意到如果一个长度为 \(len\) 的区间异或和是 \(0\),则这个区间定可以通过 \(len-1\) 的次数变成零,于是我们只需要统计这样的区间的个数,然后减掉这样的区间数就行了。code
359D(2000)
注意到 \(\exists k\in [l,r], \forall i\in [l,r], a_k|a_i \iff \min(a_l,a_{l+1},\dots,a_r)=\gcd(a_l,a_{l+1},\dots,a_r)\),于是二分区间长度,线段树维护 \(\min\) 与 \(\gcd\) 就行了。code
2040C(1600)
神秘题,场上被创飞了,从第一步就没想出来,但只要想到第一步问题就迎刃而解了。我们先考虑这个排列的生成方式。注意到我们只需要从 \(1\) 到 \(n\) 考虑填的位置(以上两句话我硬生生没想到,以后可以当做一个思维角度),于是发现每次这个数必然填在空白字符段的两端,这样每一个数都能做出最大贡献。于是我们发现一共有 \(2^{n-1}\) 种满足 \(S(p)\) 最大的排列。我们用一个二进制数来表示每一个数都放在了前端还是后端,于是我们发现这个二进制数正好对应着排名,于是把排名拆位就行了。code
622F(2600)
引理:设 \(f_k(x)=\sum_{i=1}^n i^k\),我们有 \(\deg f_k(x)=k+1\)。
证明:这里对 \(k\) 使用数学归纳法。
当 \(k=1\) 时,\(f_1(x)=\sum_{i=1}^x i = \frac{x(x-1)}{2}\),\(\deg f_1(x)=2\),成立;
如果当 \(k=n\) 时,\(\deg f_{n}(x) = n+1\);
那么当 \(k=n+1\) 时,\(f_{n+1}(x)=f_n(x)\cdot f_{1}(x)-g(x)\),其中 \(\deg g(x) < n+1\),故 \(\deg f_{n+1}(x)=\deg f_{n}(x)\cdot f_{1}(x)=n+1\),于是引理得证。
于是我们知道了 \(f_{k}(x)\) 的度数,这启发我们可以通过选择 \(k+2\) 个点来确定 \(f_k(x)\),然后利用拉格朗日插值法求出 \(f_k(n)\)。下面简记 \(f_{k}(x)\) 为 \(f(x)\)。
我们考虑选 \(x=1,2,\dots,k+2\) 的这些点,由拉格朗日插值,我们有
发现分子分母都可以预处理,于是 \(O(K)\) 直接过。code
785E(2200)
注意到交换 \(a_i,a_j\) 之后,相较之前的答案,如果 \(a_i<a_j\),\(ans+1\),否则 \(ans-1\),然后 \([1,i)\) 对答案的影响与 \((j,n]\) 对答案的影响没有任何变化,\((i,j)\) 能使 \(ans\) 加上两倍的小于 \(a_j\) 的数量再减去两倍的小于 \(a_i\) 的数量,分块维护小于 \(k\) 的数量就完了。code