做题记录啦啦啦

做题记录?

E. Deconstruction Tree

小清新题目。

一棵有 \(n\) 个节点的树从天而降,同时掉落的还有一个初始空集 \(S\)。你对这一不可能发生的事件欣喜若狂,于是做了以下 \(n - 1\) 次:

  • \(x\) 成为索引最大的叶子。
  • \(x\) 添加到 \(S\) 中(注意,如果 \(x\) 已经在 \(S\) 中,则不会有任何变化)。
  • 选择除 \(x\) 以外的任何叶子,并将其从树中删除。

确定可以组成的不同集合 \(S\) 的个数。由于这个数字可能非常大,因此输出它的模数 \(998\,244\,353\)

做法

先手玩一下整个过程。

发现集合添加的数是单调递增的。

因此可以把整个过程看成是每次选入一个值进集合。

当我们想添加一个数的时候,我们需要将它的度数删到 \(1\)

并且删的数小于集合的最大值。

我们只关心选的值,容易发现中间的过程是没有影响的。

不妨令 \(n\) 为根,这样添加 \([1,n-1]\) 的点只需要删去它们的子树。

\(sub_x\)\(x\) 的子树除 \(x\) 以外的最大值,\(y\) 为集合的最大值。

\(x\) 可选满足 \(sub_x < y < x\)\(sub_n\) 需要另外算一下。

于是我们得到了一个 dp 做法。

\(f_x\) 表示一直集合最大值为 \(x\) 的方案数。

有:\(f_x = \sum_{sub_x+1}^{x-1} f_y\)

随便树状数组或者差分一下就能得到 \(O(n\log n)\) 或者 \(O(n)\) 的做法。

CF2231E Graph Cutting 题解

一个简单自然的 \(O(n^2)\) 树上背包做法。

直接考虑枚举中心点。

转化为要选三棵子树,每棵子树内选一个点的深度之和为 \(D-1\) 的方案数。

考虑朴素背包。

\(f_{c,x}\) 为选 \(c\) 个点,深度之和为 \(x\) 的方案数。

枚举中心点,转移就每次合并一棵子树进去。

但是这样的复杂度是 \(O(n^3)\) 的。最坏可以考虑一条链的情况。

想想看怎么优化。

注意到题目只需要求深度之和为 \(D\) 的方案数。

即我们并不需要知道合并子树出来的整个 dp 数组。

根据树上背包的经典均摊结论,两个背包合代价为 \(O(siz_u \times siz_v)\),则总时间复杂度为 \(O(n^2)\)

也就是说我们其实可以知道每个点子树内的 \(f_{c,x}\)

那我们就可以将子树内与子树外在 \(O(D)\) 的时间复杂度内拼出答案:

rep(o,0,D-2) ans += d[o] * f[x][D-o-1];

CF1781F Bracket Insertion

简单记录下。

初始空串,每次概率 “()” “)(” ,问 \(n\) 次还合法的概率。

直接手玩括号折线图,考虑前缀和数组。

我们发现把前缀和作为计数对象是关键的。位置并不重要。

问题转化为:给定一个集合,初始为 {0},每次随机选择一个元素 x,有 p 的概率将 x+1,x 加入集合,有 1−p 的概率将 x−1,x 加入集合,求最终所有元素非负的概率。

概率转方案数。

我们最后不是求有多少种合法的括号串,而是求出所有的 1×3×⋯×(2n−1) 种插入空格的方式中,每种方式最后合法的概率之和

总方案数 \((2n -1)!!\)

\(f(n,x)\) 表示初始有 \(x\) ,\(n\) 次操作后仍合法的合法操作集合方案数。

转移就考虑得到 \(x,x,x+1\) .

事实上它们之间的操作是独立的

分别分配几次操作次数进去。

得到类似:

\[\huge \sum_{i=0}^{n-1} \sum_{j=0}^{n-1-i} p \cdot \binom{n-1}{i} \cdot \binom{n-1-i}{j} \cdot f(i, x) \cdot f(j, x+1) \cdot f(n-1-i-j, x) \]

再随便分离一下 \(i,j\) 可以得到 \(O(n^3)\) 做法。

CF1322C Instant Noodless

题目描述

Wu 在一次激烈的训练后感到饥饿,来到附近的商店购买他最喜欢的方便面。在 Wu 付款后,收银员给了他一个有趣的任务。

给定一个二分图,右半部分的所有顶点上都标有正整数。对于左半部分顶点的一个子集 \(S\),定义 \(N(S)\) 为所有与 \(S\) 中至少一个顶点相邻的右半部分顶点的集合,\(f(S)\)\(N(S)\) 中所有顶点上的数之和。请你求出所有可能的非空子集 \(S\)\(f(S)\) 的最大公约数

做法

注意到

\[\large \gcd(|S_u|,|S_v|,|S_u \cup S_v| = \gcd(|S_u|,|S_V|,|S_u\cap S_v|) = \gcd(|S_u|-|S_u\cap S_v|,|S_v|-|S_u\cap S_v|,|S_u|-|S_v|) \]

因为惊人的注意到:\(gcd(a,b,c) = gcd(a,b,ma + nb \pm c)\)

可以根据 gcd 的结合律和更相减损术推出。

那么每次加集合就等价于在原有集合中划分出来一点。

变成若干集合。

\(u\)\(v\) 两个点位于同一自由集合当且仅当 $ T_u = T_v$

做完了。\(T_x\)\(x\) 作为右部点对应的左部点集合。

CF1338D Nested Rubber Bands

题目描述

你有一棵包含 \(n\) 个顶点的树。你需要将这棵树转换为无限大平面上的 \(n\) 个橡皮筋。转换规则如下:

  • 对于每一对顶点 \(a\)\(b\),当且仅当树中存在一条连接 \(a\)\(b\) 的边时,橡皮筋 \(a\) 和橡皮筋 \(b\) 必须相交。
  • 每根橡皮筋的形状必须是一个简单环。换句话说,橡皮筋是一个不自交的环。

现在定义如下概念:

  • 如果橡皮筋 \(a\) 包含橡皮筋 \(b\),当且仅当橡皮筋 \(b\) 在橡皮筋 \(a\) 的区域内部,且它们不相交。
  • 一组橡皮筋 \(a_{1}, a_{2}, \ldots, a_{k}\)\(k \ge 2\))是嵌套的,当且仅当对于所有 \(i\)\(2 \le i \le k\)),都有 \(a_{i-1}\) 包含 \(a_{i}\)

做法

考虑这种包含关系会怎么产生冲突。

其一显然是必须得是独立集.

对于第二个限制,与 a,c 相交的极小的“顺次连接的环”实际上由 ac 路径上的所有点构成,因此只有当 b 到路径 (a,c) 的距离小于等于 1 的时候才合法。

所以对于合法序列 a1,a2,a3,…a**k,必有 a**i 到路径 (a1,a**k) 的距离小于等于 1,换言之,这些点一定是树上的一个导出“毛毛虫”的独立集。

CF2229F Load Unbalancing

好题。

题意

已给出一个长度为 \(n\) 的序列 \(a\),以及一个整数 \(k\)

你可以对 \(a\) 的元素重新排序任意次。然后,定义 \(f(a)\) 如下:

  • \(b\) 为长度为 \(k\) 且初始值均为零的数组。
  • \(1 \le i \le n\) 顺序处理,向 \(b\) 中的任何最小元素添加 \(a_i\)
  • 最后,\(f(a) = \max(b)\)

求出重排 \(a\)\(f(a)\) 的可能最大值。

过程比较复杂难考虑,我们直接考虑对于最后 \(a_i\) 的一组分组合法的充要。

设和最大的那一堆是 \(A\)

手玩发现是 \(A\) 堆减去 \(A_{max}\) 一定小于等于所有其他堆。

可以证明 \(A_{max}\) 是最大值一定最优。

假设现在有一组最优的分组最大值不在最后一个,我们把最后一个塞进最大值所在堆,将最大值拎出来。

容易发现此时最大值塞入最小堆后不劣。

转化问题

\(17\) 个数,值域 \(1e9\),分成 \(k\) 组,最小值最大怎么做?

如果是在序列上分组咋做?

简单二分 + 贪心。

我们这里可以移用这个做法。

即考虑排列这些数,然后在上面贪心。

\(n\) 较小的情况下排列容易想到状压 dp。

先二分答案 \(\ge k\)

我们需要将贪心所需条件记下来。

于是我们可以设一个 \(f_{st}\) 表示在把 \(st\) 集合的数塞进去后,能分多少组,余数是多少。

对这两个数值做双关键字比较即可。

CF1355F Guess Divisors Count

神秘交互

我们有一个隐藏的整数 \(1 \le X \le 10^{9}\)。你不需要猜出这个数本身。你需要找出这个数的约数个数,甚至不需要精确找出:只要你的答案的绝对误差不超过 7,或者相对误差不超过 \(0.5\),你的答案就会被认为是正确的。更正式地说,设你的答案为 \(ans\)\(X\) 的约数个数为 \(d\),那么只要以下两个条件之一成立,你的答案就被认为是正确的:

  • \(|ans - d| \le 7\)
  • \(\frac{1}{2} \le \frac{ans}{d} \le 2\)

你最多可以进行 \(22\) 次询问。每次询问你可以给出一个整数 \(1 \le Q \le 10^{18}\)。作为回应,你会得到 \(gcd(X, Q)\)——即 \(X\)\(Q\) 的最大公约数。

先分析第二个限制,即我们的 \(res \in [\frac{ans}{2},2\times ans]\)

注意到 [1e3,1e9] 中不会超过 2 个质因数。

假如我们知道前面的约数个数,则我们输出 \(res\times 2\) 即可。因为后面只可能 \(x1,2,3,4\)

但是发现不够,分组查最多查到700.

考虑第一个限制。

当答案很小的时候考虑使用。

\(res = 1\),输出 \(8\).

\(res = 2\),输出 \(9\).

\(res \ge 3\) ,则后面也不可能凑出超过2个质因数了。

于是做完了

vector<ll> table = {614889782588491410,38655288426304091,22125549654501673,316773187163046517,9879251463499721,39049078408188253,108538288030848139,309619196508457007,796229312542859009,4064625951224869,6860596063872959,10626236358872441,17092564102090369,30150641449095443,43889293834596251,60888412234461547,83850965748659689};
// 0 ~ 16

void solvemain()
{
	vector<int> p;
	// int cur = 1;
	for (auto t : table)
	{
		cout << "? " << t << endl;
		int x;cin >> x;
		for (int i = 2;i <= x / i ; i ++)
		{
			if (x % i == 0)
			{
				p.pb(i);
				x/= i;
			}
		}
		if (x > 1) p.pb(x);
	}
	
	const int m = 1e9;
	ll ans = 1;
	for (int i = 0 ;i < sz(p);i+=2)
	{
		if (i == sz(p ) - 1)
		{
			int x = p[i];
			while (x <= m / p[i]) x *= p[i];
			cout << "? " << x << endl;
			cin >> x;
			ans = ans *( (int)round(logl(x) / logl(p[i])) + 1);
		}
		else{
			int tp = p[i];while (tp<=m/p[i])tp*=p[i];
			int tp2 = p[i+1];while (tp2<=m/p[i+1])tp2*=p[i+1];
			cout << "? " << (ll)tp*tp2 << endl;
			int x;cin >> x;
			int c1 = 0 ,c2 = 0 ;
			while (x % p[i] == 0) x /=p[i],c1++;
			while (x % p[i + 1] == 0) x /=p[i + 1]  ,c2++;
			ans = ans * (c1 + 1) * (c2 + 1);
		}
	}
	if (ans == 1)
	{
		cout <<"! "<< 8 << endl;
		return;
	} 
	if (ans == 2)
	{
		cout <<"! "<< 9 << endl;
		return ; 
	}
	cout << "! "<<ans * 2 << endl;
}

CF1385F Removing Leaves

题目描述

给定一棵包含 \(n\) 个顶点的树(无环连通图)。这棵树是无根的——也就是说,它只是一个无环的连通无向图。

每一步,你可以选择恰好 \(k\) 个叶子(叶子是指只与一个其它顶点相连的顶点),这些叶子都与同一个顶点相连,并将它们以及与它们相连的边一起删除。也就是说,你可以选择叶子 \(u_1, u_2, \dots, u_k\),它们都与同一个顶点 \(v\) 相连(存在边 \((u_1, v)\)\((u_2, v)\)\(\dots\)\((u_k, v)\)),然后删除这些叶子和这些边。

你的任务是,若每一步都最优地删除叶子,求最多可以进行多少次这样的操作。

做法

直接按题意模拟任意删都是对的。

证明

假设存在最优的操作序列 opt。

首先我们贪心的操作一定会出现在opt。

假设不出现,我们操作的那几个节点永远不会改变。

因此操作是优的。

考虑证明我们接下来一步贪心的操作在 opt中一定也是第一步。

我们发现先放在第一步完全不劣, 因为它操作了也不会影响任何东西,甚至有帮助。

依次类推,我们归纳证明出了贪心是最优的。

trick:证明一个操作 S 是优的,我们只需要假设存在更优的opt

归纳证明:S的第一个操作一定出现在opt中且可以放在第一个

CF2208D2 Tree Orientation (Hard Version)

你打算随意给每条 \(n-1\) 条边赋予一个方向。你发现了一张记录每一组有序对 \((u,v)\) 满足 \(1\le u,v\le n\),都标记了 \(u\) 是否能够到达 \(v\)

你想根据这份信息,推断出可能的树的结构以及边的方向。如果有可能,请构造出任意一种解;如果有多个解,只需给出其中一种即可。

做法

\(O(n^3)\) 做法很简单。

枚举 \(x,y\),存在边 \(x \rightarrow y\) 当且仅当不存在 \(x \rightarrow j \rightarrow y\) 之间可达。

考虑优化。

我们枚举的这三个哪个是可以被优化的呢?

事实上是 \(j\),因为我们不需要枚举每一个 \(j\) ,我们只需要判定 \(x\) 的邻居 \(j\) 即可。

这样复杂度就变为了 \(O(n^2)\) 了。

计数好文

https://www.cnblogs.com/gsc0618china/p/19086852

CF1404C Fixed Point Removal

给定一个长度为 \(n\) 的正整数数组 \(a_1, \ldots, a_n\)。每次操作,你可以选择一个下标 \(i\),使得 \(a_i = i\),并将 \(a_i\) 从数组中移除(移除后,剩余部分会自动拼接)。

数组 \(a\) 的“权重”定义为你最多可以移除的元素数量。

你需要回答 \(q\) 个独立的询问 \((x, y)\):将 \(a\) 的前 \(x\) 个元素和后 \(y\) 个元素都替换为 \(n+1\)(使它们无法被移除)后,\(a\) 的权重是多少?

做法

\(v_i = i - a_i\)

每次操作化为选择一个 \(0\),令后面的数 \(-1\),问最多删除多少数。

容易列出 \(dp\)

\[\large f_{i + 1} = f_i + [f_i \ge v_i] \]

题目即求区间做一遍 dp 的值。

可以先把 \(f\) 扩展为 \(f_{l,r}\) 表示区间的 dp 值。

对于这种区间求值的问题。

有几种做法:矩阵动态dp,维护dp值端点,扫描线。

我们考虑扫描线。

由于往后添加数比较简单,我们考虑 \(R\) 往后扫。

1

考虑添加 \(a_r\) 怎么维护出 \(\forall i<r,f_{i,r}\)。每个位置 \(i\) 维护的即为 \(f_{i,r}\)

我们已知 \(\large f_{i,r-1}\)

则当 \(f_{i,r-1} \ge a_r\) 时,\(f_{i,r} \leftarrow 1\)

Observation:单调性

这里的性质是:固定的端点,另一端越长答案越大。

于是我们可以二分出需要加的位置,做区间加,可以用树状数组上二分。

int search(int k,int limit)
{
	int res = 0 ,p = 0 ;
	for (int i = __lg(N);i >= 0;i--)
	{
		int ne (p + (1<<i));
		if (ne <= limit && res + c[ne] >= k) 
			res += c[ne],p = ne;
	}
	return p;
}
vector<int> ans(q + 2);
rep(r,1,n)
{
    int pos = search(a[r] , r - 1) ; 
    add(1,pos,1),add(r,r,(a[r]==0));
    for (auto [l , id] : Qr[r])ans[id] = query(l);
}
rep(i,1,q) cout << ans[i] << endl;

  • 这个 DP 值关于初始状态一定具有单调性
  • 特征: 题目给了很多区间询问,而且不带修改。

2

其实用 \(L\) 从右往左扫也是可做的。因为有观察:右端点不会影响点能不能删,只起到一个划定范围的作用。

假设增加 \(a_l=0\)

则会有一个 \([l + 1,n] \leftarrow 1\) 的贡献。

但是会有连锁反应很烦。

trick:连锁反应?!

事实上不烦。

因为我们只需要维护 \(c_i\) 表示 \(i\) 现在能不能被删。

在扫描线的过程中,\(c_i\) 只会被改一次。

而我们只需要线段树(维护区间最小值及其位置,支持区间加减)和一个树状数组(BIT)(维护已经删除的元素个数,用于回答询问)来模拟这个过程即可。

P14568 【MX-S12-T3】排列

trick:排列计数插入法?!

求出有多少个 \(1 \sim n\) 的排列 \(a_1, \ldots, a_n\) 满足以下条件:

对于每个下标 \(i\)\(1 \le i \le n\)),

  • \(a_i < \min_{j < i} a_j\)
  • \(a_i > \max_{j < i} a_j\)
  • \(a_i < \min_{j > i} a_j\)
  • \(a_i > \max_{j > i} a_j\)

对每个下标 \(i\),其满足四种情况中的哪一种由输入给出。答案对 \(998244353\) 取模。

对于排列计数,可以考虑按顺序插入,只考虑相对位置。

具体地,考虑将 \(a_i\) 插入前面构成的序列的哪里,有 \(i\) 个插入点。

再根据题目的限制 dp。

当我们要把某个数插入到位置 \(i\) 的时候:

  • \(op_i=0\) 时,要求其左侧最终没有比它更小的数。即在插入按递增的过程中,这只能在当前它已剥余区间的最左端时满足。

  • \(op_i=1\) 时,同理只能在剩余区间最右端才能满足。

  • \(op_i=2\) 时,要求其左侧最终的最大值小于它。递增插入时,这等价于当前已放的左侧不存在。

  • \(op_i=3\) 时,同 \(op_i=2\)

所以我们就可以列出来一个状态

\[f_{i,j} \]

表示当前已经插入了前 \(i\) 个位置,并且还剩余 \(j\) 个位置可插。

转移是容易的:

  • \(op\in\{0,1\}\) 时,每次插入相当于新增一个可插入位置。直接由

\[f_{i-1,j-1} \]

转移过来即可。

  • \(op\in\{2,3\}\) 时,放这个数时它要放在已有的已预留的位置中的某一个,放完后可插入位置数不会增加,而且 \(j\) 可以变成任意不超过原来的值,因此由

\[f_{i-1,k}\qquad(\forall k\le j) \]

转移而来。

posted @ 2026-05-30 19:01  codwarm  阅读(7)  评论(0)    收藏  举报