海亮第二次集训总结

2025/2/14 模拟赛

T1

抽象贪心。

题意

给定一颗 \(n\) 个点的树。每次你可以消除没有父亲的 \(m\) 个点(它们被同时消除,必须都预先没有父亲)。求删除所有点的最少次数。对所有 \(m\in [1,n]\) 求答案。

要求 \(O(n)\) 时间复杂度。

解法

一种错误的贪心是每次删除儿子数目最多的节点。

正确的贪心:每次删除子树深度最深的节点。感性理解是,如果有一些很深的点很晚才被删除,它们就会成为累赘,所以应该尽快删除它们。

然后,有一种十分假的做法(但是能得 96pts):令 \(cnt_i\) 表示深度大于等于 \(i\) 的节点个数,\(num_i\) 表示深度为 \(i\) 的节点数,找到第一个 \(p\) 使得 \(num_{p+1}>m\),那么答案就是 \(p+\lceil \frac{cnt_{p+1}}{m}\rceil\)(前 \(p\) 层每层一次删完,后面的每次删 \(m\) 个)。

这样显然有问题,比如假设 \(p\) 层有很多叶子,那么 \(p+1\) 层往后的节点数就会很稀少,就没法一次删 \(m\) 个。

我们就考虑基于上述算法改进。首先如果我们这一层比 \(m\) 个要多,我们就贪心消掉子树最深的 \(m\) 个,剩下的放到下一层去(反正我这一层也不会消除它们了)。因此,我们就相当于是把分界线向下推移了。这样一直推移下去,我们就可以让我们的消除行为合法化。(?)

于是我们知道答案是 \(\max\limits_{i=1}^{depth}(i+\lceil \frac{cnt_{i+1}}{m}\rceil)\) 中的一个。是哪一个呢?我们知道只有一个是合法的,其余的都是不合法的,而不合法的得出的答案会偏小,所以答案是 \(\max\limits_{i=1}^{depth}(i+\lceil \frac{cnt_{i+1}}{m}\rceil)\)

如何快速对于每个 \(m\) 求出答案呢?只需斜率优化即可。

T2

比较难。记住这个 trick

Trick:最大异或和

简单来说就是按位贪心。但是,如果是给定一系列串 a,多次询问一系列串 b 与 a 异或的和的最大值,就可以用 Trie。

2025/2/15 周赛

好不容易打到了rank 2!何天成太巨了。

T5

Trick:二分求中位数最值

求中位数最值(最大值、最小值)时,一般采用二分答案:将小于\(mid\) 的设为 \(-1\),大于 \(mid\) 的设为 \(1\),等于 \(mid\) 的按情况设置,这样就变成了查找是否有区间(集合)的总和大于等于 \(0\).

T6

抽象 状压DP+差分。

我和何天成努力了一下午终于把 \(O(2^nm+3^n)\) 的做法设计了出来。可以看看他的题解,我真的不想再写一遍了。

何天成的题解

注:最后转移时说的那一大堆,说白了其实就是两点:我为人人的转移(不仅时间效率更好,而且性质更好);递推地求出每个增量 \(S_0\) 的贡献。

关于 DAG 计数

经典例题 BZOJ286。

这题的题意是求包含 $ n $ 个点的带标号 DAG 数量,做法如下:

设 $ f(i) $ 表示 $ i $ 个点的 DAG 个数。考虑图中有多少个点入度为 0,在去掉这些入度为 0 的点后图仍然会是 DAG,就可以基于此进行转移。

对于 $ n $ 个点钦定 $ i $ 个入度为 0 的方案数为:\(f(n-i) \cdot \binom{n}{i} \cdot 2^{i(n-i)}\)

注意到恰好 0 个点入度为 0 的方案数为 0,于是有:\( \sum_{i=0}^n (-1)^i \cdot \binom{n}{i} \cdot 2^{i(n-i)} \cdot f(n-i) = 0\)

移项可得递推式:\(f_n = \sum_{i=1}^n (-1)^{i+1} \cdot \binom{n}{i} \cdot 2^{i(n-i)} \cdot f_{n-i}\)

套用同样的计数思路,得到一般形式的转移式:

\[f_i = \sum_{j \subseteq i} (-1)^{|j|} \cdot f_{i \setminus j} \cdot 2^{|j| \cdot |i \setminus j|} \]

其中 $ E(j, i \setminus j) $ 表示点集 $ j $ 向点集 $ i \setminus j $ 的连边个数,即 $ |j| \cdot |i \setminus j| $ 。

2025/2/16 杂题选讲

AT_agc018_b [AGC018B] Sports Festival

第一眼,二分?想了想,并不是很好做。

我们很容易能想到一种贪心:先把所有活动开着,算出每个活动的排队人数,然后取消人数最多的活动,把里面的人都分散到其他活动中。由于这样做答案可能会变大,我们每做一次就更新一次答案。

这个过程可以用 vector 存每个活动的人,用指针指向每个人当前在的活动是他列表中的第几项,做到 \(O(m^2)\)

怎么说明贪心是对的? 我们考虑调整法。把我们通过贪心逐步删除的活动列成一个序列 \((a_1,a_2,\dots,a_{m-1})\),那么贪心就会考虑到 删除该序列的前缀 的所有情况。

假如有一种最优解贪心没有考虑到,那么它就不是 删除该序列的前缀 的方案。此时该序列中一定存在 第一个没有删除的位置,且这个位置之后还有被删除的活动。

此时我们的答案不可能小于这个活动当前的人数,而删除了其它的活动反而可能增大这个活动的人数,因此我们删除这个活动后面的任何活动都不可能产生任何更优秀的解。所以直接舍弃后面选中删除的活动即可调整为贪心的解。

综上,贪心一定能得出最优解。

ARC110D Binomial Coefficient is Fun

此题给予了很重要的经验:多项式生成函数处理卷积。见我的洛谷博客。

ARC110F Esoswap

抽象紫题。相当难想但是代码难度大约是红。

“注意到”一个位置被操作 \(n\) 次后一定是 \(0\)。这是因为你每次操作位置上的数都和以前不同,而变成 \(0\) 后就不会再操作了。我们对 \(0\) 的上一位进行 \(n\) 次操作后,这个位置上一定是 \(0\) 且下一位一定是 \(1\)。这是因为只有这一位是 \(1\) 时才能置换出 \(0\),而置换出 \(0\) 后就不动了。

以此类推,我们倒着枚举每个点并对它们各做 \(n\) 次操作就能使序列有序了。

2025/2/17

T1

题面

有一个树有 n 个点,初始树上有 k 个棋子(位置互不相同)。
现在每个时刻可以让一个棋子移动一条边。
我们需要让这 k 个棋子总过走过 n 个点,而且每个点只能被恰好一个棋子走到(可以被这个棋子走多
次,但只能是这个棋子)。
求最小的时刻使得 n 个点都走过。

下位蓝的树形DP,但我写了3.5h,我好菜】

Trick

从根开始走路,每次走过一条边,遍历树上所有节点所需的最少步数是 \((节点数-1)\times 2-距根最远点的距离\),因为一旦走到最后一个点就不用返回了。

剩下的部分是经典树形 DP:树上选取 \(k\) 条 点意义下不交的、有一个端点是关键点的链,求长度和的最大值。

\(dp_{i,0/1/2/3}\) 表示考虑 i 的子树的总答案,i 点的状态是:没选、被子树内的链选了、被子树内的链选了而且可以向外延伸、从子树外伸进来的链延伸到了最大长度。

详情见我的代码:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=5e5+10;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll n,k;
bool p[N];
ll dp[N][4];//不选,被子树中一条链选了,被下面来的链选了,被子树外来的链选了 
vector<ll> a[N];
void dfs(ll x,ll fa)
{
	ll sum01=0;
	for(auto to:a[x])
	{
		if(to==fa) continue;
		dfs(to,x);
		sum01+=max(dp[to][0],dp[to][1]);
	}
	if(p[x])
	{
		dp[x][0]=dp[x][3]=-inf;
		dp[x][2]=dp[x][1]=sum01;
		for(auto to:a[x])
		{
			if(to==fa) continue;
			dp[x][1]=max(dp[x][1],dp[to][3]+1+sum01-max(dp[to][0],dp[to][1]));
		}
	}
	else
	{
		dp[x][0]=dp[x][3]=sum01;
		dp[x][1]=dp[x][2]=-inf;
		for(auto to:a[x])
		{
			if(to==fa) continue;
			dp[x][2]=max(dp[x][2],dp[to][2]+1+sum01-max(dp[to][0],dp[to][1]));
			dp[x][3]=max(dp[x][3],dp[to][3]+1+sum01-max(dp[to][0],dp[to][1]));
			dp[x][1]=max(dp[x][1],dp[to][2]+1+sum01-max(dp[to][0],dp[to][1]));
			for(auto to2:a[x])//复杂度上有点问题,不过想优化也很简单,懒得写了
				if(to!=to2 && to2!=fa)
					dp[x][1]=max(dp[x][1],dp[to][2]+dp[to2][3]+2+sum01-max(dp[to][0],dp[to][1])-max(dp[to2][0],dp[to2][1]));
		}
	}
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	freopen("bo.in","r",stdin);
	freopen("bo.out","w",stdout);
	cin>>n>>k;
	for(ll i=1;i<=k;i++)
	{
		ll x;
		cin>>x;
		p[x]=1;
	}
	for(ll i=1;i<n;i++)
	{
		ll u,v;
		cin>>u>>v;
		a[u].push_back(v);
		a[v].push_back(u);
	}
	dfs(1,0);
	cout<<(n-k)*2-max(dp[1][0],dp[1][1]);
	return 0;
}

Tree Interval Queries

80pts:对每两个区间处理:前者的父亲是否有的在后者里,若是则答案-1.使用主席树预处理维护 dfn 前 i 的点的父亲的 dfn,查询时直接区间查询即可。

2025/2/18 模拟赛

两个很糖的举措:睡觉很晚;考试时手贱用火绒诊断网络把我局域网配置搞没了,最后没交上程序。

T1

cjy的题解(包括题意简述):https://www.cnblogs.com/chenaknoip/p/18721915

博弈论。没学过SG函数的我:🤡

SG函数是什么就不多说了,这里讲一下 Trick

对于每次可以删除一个子树,删除根节点就输的游戏,SG函数满足 \(sg(x)=\bigoplus\limits_{to\in son_x}sg_{to}+1\)

证明如下:考虑称呼删除根结点就赢的游戏的SG函数为 \(sg'\)。这个新游戏的有向图比原游戏增加了一个根被删除的状态,SG值 为 \(0\)。我们要证明每个状态节点的 \(sg'(x)=sg(x)+1\)。使用数学归纳法证明:对于一个状态,假设其出边指向的状态都满足上述结论,那么它们的SG值就比原来的 \(+1\),而此时新加入的节点又弥补了 \(0\) 的位置,于是本状态出边连向的sg值集合的mex就+1,因此本状态sg值+1,得证。

T2

广义串并联。不会。

2025/2/19 下午图论

Tricks

  • 有时可以把点权放到边权上去,或者反过来。
  • 有时可以建立虚点,用作桥梁或者源点。
  • 一条边能否在 MST 上取决于已有 MST 上 \(u,v\) 路径的最大值。

重要定理

对于一个图的任意 MST(最小生成树),构成它的边的权值集合全都是相同的。比如都是 \(\{1,1,1,2,2,3\}\),没有 \(\{1,1,1,1,3,3\}\),根据 Kruscal 的过程显然。

也就是说,一个图可以按边权分为若干层,不论 MST 选择了哪些边,每一层选取的边的总数是固定的。

2025/2/20 杂题选讲(奇怪科技)

T1

\(n\) 个二元组 \((a_i, b_i)\),进行 \(m\) 次操作,每次操作为以下两种之一:

  • 给定 \(x, a', b'\),将 \((a_x, b_x)\) 修改为 \((a', b')\)
  • 给定 \(x, l, r\),求 \(\max\limits_{i=l}^r a_i\times x + b_i\)

\(n, m ≤ 5 × 10^5\)

QOJ 7766

QOJ 2566

CF1896H2

对于 a,b 两个 01 串,求其每个循环移位与异或卷积有关系。

QOJ 7838

P11592

P9732

CF1458F

树上邻域理论

对于一个点集,其所有直径的中点是相同的。

以该点为圆心,覆盖点集 S 的最小圆。

2025/2/21 模拟赛

被吕沐澐碾爆了,她 86pts 碾压机房包括学长在内的所有人。

T1

考场思路过程

前两个小时很没状态,眼皮都睁不开,后来发现这个问题能转化为图论。又过了好久,我想起要找出不变量,所以发现度数貌似是唯一的不变量,手模一些样例后发现度数好像就是决定答案是否有解的唯一关键因素。然后就写了暴力和这个分,拿到了44pts。

题解

由 Deepseek R1 基于源题解生成。

题目分析
考虑每次操作后,每个点的度数都不变,因此有解的一个必要条件是图 \(G\)\(H\) 中每个点的度数相同。若此条件不满足,则问题无解。否则我们断言一定有解。

构造方法
\(1\)\(n\) 依次考虑每个点 \(v\)。假设它在 \(G\) 中的邻域是集合 \(S\),在 \(H\) 中的邻域是集合 \(T\)

  1. \(S = T\),则可以直接忽略点 \(v\)
  2. 否则必有 \(|S| = |T|\)(因度数相同)。我们需找到一条边进行调整:
    • 随意选取 \(u \in S \setminus T\)\(w \in T \setminus S\)。目标是通过操作删除边 \(uv\) 并添加边 \(wv\)
    • 若存在点 \(w\) 满足 \(w \in T \setminus S\) 且边 \(uw \notin H\),则直接操作 \((u, w, v)\) 即可完成调整。
    • 若上述条件不满足,则必存在点 \(u' \in S \setminus T\) 满足边 \(u'w \in G\)\(u'w \notin H\)。此时可将最后一次操作设为 \((u', w, v)\),从而删除边 \(u'v\) 并添加边 \(wv\)

正确性证明
若无法找到第一种情况的 \(w\),则说明 \(\forall w \in T \setminus S\),边 \(uw \in H\)。同理,若无法找到第二种情况的 \(u'\),则说明 \(\forall u' \in S \setminus T\),边 \(u'w \notin H\)。若两者均不成立,则必有 \(S \setminus T = T \setminus S = \emptyset\),即 \(S = T\),与前提矛盾。因此至少存在一种可行操作。

复杂度分析
直接模拟该过程的复杂度为 \(O(n^3)\),利用 bitset 优化邻域查询可降至 \(O\left(\frac{n^3}{\omega}\right)\),其中 \(\omega\) 为机器字长。


:本题解核心思路基于度数守恒性质,通过局部调整策略逐步对齐邻域,最终构造出合法操作序列。关键点在于证明两种操作至少存在一种可行方案,从而避免循环依赖。

T2

T3

2025/2/23

题单链接

开题顺序错误。应先开 T2.

T1

问题描述

我们用一个长度为 n 的数列 a 和一个长度为 m 的排列 p 表示一个繁分式,其中 ai 表示从上到下第 i 个数,pi 表示第 i 条分数线的长度,分数线越小越早算。

现给出数列 a 和排列 p, m次询问,每次询问 a_{[l-1,r]},p_{[l,r]} 的繁分式的值,对 998244353 取模,部分数据强制在线。

数据量要求 O(n)

解法

考场打了个记忆化搜索骗了52pts,疑似数据没绑subtask导致的。

学习 zkw 线段树,广义线段树(即不在mid分成两段)等或许有助于此题。

T2

题面简述

给定一张 2n 个点 m 条边的有向图,无重边自环,初始时所有点未被染色。

现在 Alice 和 Bob 轮流进行点染色操作。Alice 对点 1,2 染色,然后 Bob 对点 3,4 染色,Alice 再对点 5,6 染色,以此类推。每次染色时,恰好选一个点染黑色,另一个点染白色。

假如结束之后,不存在一条边从黑点指向白点,则认为 Alice 赢了。否则认为 Bob 赢。

问在最优策略下,谁会获胜?

解法

部分分给了 “保证每条边都在同一个人的管辖范围内”(20pts)那么2-SAT一眼秒了。实测能得55pts,加上暴力有70pts。(只是判断是否有一条边完全在Bob的范围内的做法竟然有35pts)最后10min写的,建边建设错了。

正解:类似地,我们考虑保持原先的2-SAT。但是:

  • 只要Bob能在这个2-SAT图上从一个占据的节点到达另一个(通过多条边的路径也行),他就赢了(因为它可以把前者设为 1,后者设为 0)。
  • 另外,如果有一条边将 Alice 控制的点 x 与 Bob 控制的点 y 直接相连,y>x,根据边的方向需要限制 x 只能取 0/1(否则 Bob 就可以根据 Alice 的取值赢下比赛)。
  • 除此之外没有别的情况了。

Trick

2-SAT 如果想要限制某个点的取值为 0/1,可以在取值不为预期的时候制造矛盾。如想要令 x=1,就从 x_0\rightarrow x_1 。

2025/2/24 模拟赛

T1

题面

小 T 学会了如何用线段树处理区间加区间求和问题。

小 T 有一个长为 \(2^m\) 的数组 \(a\),下标从 \(0\) 开始。数组 \(a\) 初始全为 \(0\)

小 T 对这个数组建了一棵线段树。他每次会在线段树的一个区间 \([l, r]\) 上做修改/查询,但他每次会给出两个参数 \(k, t\),并只修改/查询满足 \(i \bmod 2^k = t\)\(a_i\)

具体来说,有 \(n\) 次操作,每次操作形如:
1 l r k t w 表示对所有 \(i ∈ [l, r]\)\(i \bmod 2^k = t\),将 \(a_i\) 增加 \(w\)
2 l r k t 表示对所有 \(i ∈ [l, r]\)\(i mod 2^k = t\),询问这些 \(a_i\) 的和。

保证 \([l, r]\) 代表一个线段树上的区间,且区间长度 \(≥ 2^k\),也就是说,保证 \(0 ≤ l ≤ r ≤ 2^m − 1\) 且存在非负整数 \(p ≥ k\),满足 \(r − l + 1 = 2^p\)\(l\)\(2^p\) 的倍数。

做法

考场上想到了每次询问和修改的下标都是有一个固定的前后缀,剩下部分任意。但仍然没想对。

简单来说,就是离线。枚举询问前后缀长度的 \(L_1,L_2\),那么扫描一遍操作,对于每个修改我们只需保留一些位(具体来说就是修改的有效位和 \(L_1,L_2\) 的并),并贡献到对应类型的位。


  • 注意到,每次修改或查询定位到的数组下标,二进制一定形如 011? ? ? ? 1010,也就是说确定了二进制中的一段前缀和后缀。
  • 我们先枚举前缀和后缀的长度分别为 \(L_1, L_2\),考虑求出所有前后缀长度为 \(L_1,L_2\) 的查询的答案。
  • 暴力的想法是,枚举每个修改和询问,接着计算贡献。计算贡献的方法是,首先
    比较不是 ? 的部分是否相同,如果不相同贡献为 \(0\),否则贡献为相同的问号个数。
  • 注意,我们已经枚举了询问中 ? 的位置,所以对于一个修改,它能对询问造成的贡献只需看不是 ? 的部分就能确定。所以我们直接记录它不是问号部分的
    \(01\) 序列具体是什么,以及它前后缀的长度。
  • 对于询问,我们枚举对它造成贡献的修改的前后缀长度,接着只需要查询对应
    \(01\) 序列的贡献之和。
  • 用 hash 表维护即可。时间复杂度 \(O(nm^2)\)

T2

freopen写错挂了20.乐

2025/2/25 模拟赛

T1

抽象题目。

首先我很轻易注意到了 轮换 可以转化为 划分。然后我竟然没想出 \(O(n^2)\) 的 DP?(虽然想出了也写不出高精度就是了)

正解非常简单:\(\prod\limits_{i=1}^n \gcd(a_i,i-1)\)

证明

首先轮换没什么实际意义,其实问题就是求将序列划分为若干有序集合的所有方案的贡献和。

先对 \(a_i\) 排序。(这我没想到)

列出 \(O(n^2)\) 的 DP:\(f_{i,j}=a_i\times f_{i-1,j-1}+(i-1)\times f_{i-1,j}\)。加号前那一项表示新划分一个集合,后一项表示分到前面的集合中(为什么是 \((i-1)\) 而非 \(j\)?因为轮换是有序的)。答案是 \(\gcd(f_{n,1},f_{n,2},\cdots,f_{n,n})\)

考虑追踪 \(f_n\) 这一行中的每个因数是怎么来的,会发现它们一定出现在了某次的 \(\gcd(a_i,i-1)\) 中。

为什么呢? \(f_{i-1}\) 行的公因数会随着转移被继承到 \(f_{i}\) 行。根据Bezout定理,\(ax+by=c\) 的充分必要条件是 \(\gcd(a,b)\mid c\)。令 \(a\leftarrow a_i,b\leftarrow i-1\),我们就证明了转移的加法不会产生除了 \(\gcd(a_i,i-1)\) 以外新的公因数。

于是得证。

T2

更唐了。最后 1min 交了一份打表多打了一行的代码,但是打表部分少了第 0 行导致变成 15pts。而我原本那份代码是 25pts。

观察大样例会发现 \(k=2\) 时答案是 \([n\bmod 4=3]\).

Trick

对 2 取模的问题可以考虑让某些贡献两两抵消。

T3

DP 套 DP。这是该算法的模板题。https://www.luogu.com.cn/problem/P4590

posted @ 2025-02-14 23:05  Luke_li  阅读(32)  评论(0)    收藏  举报