AGC001,002,003

AGC001

AGC001D Arrays and Palindrome

这个题的策略类似于“接头”。

首先有一个前置重要观察,就是最多 \(a\) 中只有两个奇数。原因是我们考虑将其转化为图论模型,一个大小为 \(a_i\) 的块会连 \((a_i / 2)\) 下取整条边。假如出现了 3 个以上的奇数那显然就无解了。所以奇数最多为两个。

对于偶数的 \(a_i\),可以利用如下的“内卷模型”:

image.png

这样就是耗掉一个红色插头,新建一个绿色插头。

也可以“建头”:

image.png

奇数的唯一不同点在于内卷模型没有了,建头模型还有。

那由于最多只有 2 个奇数,那么放到两边之后用建头模型即可(偶数也可以建头模型),中间的偶数用内卷模型。复杂度 \(O(N)\).


AGC002

AGC002C Knot Puzzle

首先容易观察到假如 \(a_i + a_{i+1} \geq L\) 对于所有 \(i\) 均成立则一定无解,因为最后一步一定是两个分开。

假如存在,那么可以通过从两边削的方式构造出一组方案,故的解。

AGC002D Stamp Rally

Kruskal 重构树板题。

AGC002E Candy Piles

神必博弈论。

我开场以为我会了个 \(n^2 a_i\) 的做法,然后就蒙了。后来发现排个序就是 \(na_i\) 的。

具体来讲,我们令 \(win(i, j)\) 表示当前所有数都被减了 \(i\),且目前 \(j+1...n\)\(a\) 都被删光的情况下,先手是否有必赢策略。

首先判掉 \(i=a_1\)\(j=1\) 的两类情况,接下来假设两种操作都能搞,那么转移到 \(win(i+1, j)\)\(win(i, j-1)\). 然后就自闭了。。。

注意到一个精妙的性质,就是这个东西等价于在一个图上游走,游走到边界的人算输。同时一条对角线上的输赢状态相等,证明如下:

  1. 假如 (x, y) 输,那么其左边和下边的点赢,(x-1,y-1) 输。

  2. 假如 (x, y) 赢,(x-1, y-1) 赢,那么这一对角线左边和右边的对角线必有一条输,则 (x-2, y-2) 输。

然后就只需要求 \(O(n)\) 个点的 dp 值了。

#include <bits/stdc++.h>

const int N = 1e5 + 5;
int n, A[N];
int main() {
	std::ios::sync_with_stdio (0);
	std::cin >> n;
	for (int i = 1; i <= n; ++i)
		std::cin >> A[i];
	
	sort (A + 1, A + 1 + n, std::greater <int> ());
	int p = 0;
	while (A[p + 1] >= p + 1) ++p;
	--p;
	
	int cp = p, odd1 = 0, odd2 = 0;
	while (A[p + 1] >= cp + 1) ++cp, odd1 ^= 1; cp = p;
	while (A[p + 1] > cp) ++p, odd2 ^= 1; 
	std::cout << ((odd2 && odd1) ? "Second" : "First") << '\n';
	return 0;
}

AGC002F Leftmost Ball

被我乱 dp 搞过去的一道题。

首先我们发现一个性质,就是我们考虑枚举这些白色球的位置,设为 \(p_1 p_2 ... p_k\),那么方案数实际上是

\[\prod_{i=1}^{N} \binom{i \times K-p_i}{K-1} \]

问题变成了怎么求这个东西。

考虑设 \(dp_{i, j}\) 为前 \(i\)\(p\)\(p_i = j\) 的方案数,优化一下可以做到 \(O(n^2 k)\).

我们发现要做到比 \(O(n^2 k)\) 小,那么 \(p\) 的具体值肯定不能出现在 \(dp\) 过程中。我们发现一个关键性质,就是每一个颜色肯定是先出现白色球,再出现有颜色的,也就是说我们可以对于每一个颜色分两类考虑。

也就是说实际上你是考虑决策一坨东西的拓扑序:

image.png

其中一个颜色的位置要一起决策。所以你就设 \(dp_{i, j}\) 表示出现了 \(i\) 个白球,\(j\) 种颜色,然后决策新出现白球还是新出现颜色即可。

AGC003

AGC003B

我觉得贪心题的难度都很不正常啊。。。

经典的观察上界——构造上界的构造方法。本题观察到若 \(a_i \geq 1\),则上界为 \(floor(S/2)\). 构造方法如下:

for (int k = i; k < j; ++k) {
	ans += a[k] / 2;
	a[k] %= 2;
	if (a[k] && a[k + 1]) ans++, a[k]--, a[k+1]--;
}

那么据此推理可以得到中间有 0 的情况。

AGC003D

我写了一个憨批做法:对每一个数进行 pollard-rho 分解,每个质因子幂次 %3,这样就可以找出唯一一种不能同时选的东西。这是一个基环树森林,选最大独立集是一个经典的 dp.

AGC003E

这题是一个对于出现次数很聪明的转化,就是倒推。

具体地,先用单调栈将 \(q_i\) 变成严格递增,随后考虑这样一个暴力:

  1. 把第 \(Q\) 个版本的数列设成全 \(1\),大小为 \(q_i\).

  2. 构造第 \(Q-1\) 个版本,大小为 \(q_{i-1}\) 的数列,方法:第 \(i\) 个位置是所有第 \(Q\) 个版本 \(\mod\ q_{i-1} = i\) 的下标对应的值的和。

  3. 如此往复构造第 \(Q-2\) 个版本,第 \(Q-3\) 个版本...

  4. 输出第 \(1\) 个版本。

也就是说这是一个反累计的问题,也是一种贡献法。

那么考虑 \(2\) 操作和 \(3\) 操作如何优化复杂度。假如有整除关系很好做,所有的值都是一样的,所以我们令 \(t_i\) 表示 \(i\) 版本是几倍的 \(q\) 版本,倒推即可。问题转化为如何将边角的部分也直接加到答案里。

方法是这样的。我们定义 \(f(len, w)\) 为将长度为 \(len\) 的边角料内的所有值加上 \(w\) 的操作。那么考虑找到最后一个比 \(len\) 小的长度对应的下标 \(p\)。假如找不到 \(p\),那么一定意味着原序列就比 \(len\) 长,这时候我们用一个差分数组来支持前 \(len\) 个数 \(+1\). 不然我们就考虑让那一个长度对应的 \(t\) 加上若干个整块的值,随后递归下去处理。

这样每次复杂度至少减半,加上二分复杂度是 \(Q\log^2n\). 很妙的一个题。

#include <bits/stdc++.h>
typedef long long ll;
const int AZB = 100100;
int N, Q, par[AZB]; 
ll q[AZB], q1[AZB], cnt, tm[AZB];
ll S[AZB];
void f (long long len, long long tms) {
	int pos = std::upper_bound (q, q + cnt, len) - q; // first place > len
	if (pos == 0) {
		S[1] += tms;
		S[len + 1] -= tms;
		return ;
	}
	else {
		tm[pos - 1] += (len / q[pos - 1]) * tms;
		f (len % q[pos - 1], tms);
	}
}
int main() {
	std::ios::sync_with_stdio (0);
	std::cin >> N >> Q;
	for (int i = 1; i <= Q; ++i) std::cin >> q[i]; q[0] = N;
	std::stack<int> stk;
	
	memset (par, -1, sizeof (par));
	for (int i = 0; i <= Q; ++i) {
		while (!stk.empty() && q[stk.top()] >= q[i]) 
			stk.pop();
		if (stk.size()) par[i] = stk.top();
		stk.push(i);
	}
	int cur = Q;
	while (~cur) q1[cnt++] = q[cur], cur = par[cur];
	std::reverse (q1, q1 + cnt);
	memcpy (q, q1, sizeof (q1));
	tm[cnt - 1] = 1;
	for (int i = cnt - 1; i >= 1; --i) {
		tm[i - 1] += (q[i] / q[i - 1]) * tm[i];
		f (q[i] % q[i - 1], tm[i]);
	}
	
	S[1] += tm[0];
	S[q[0] + 1] -= tm[0];
	for (int i = 1; i <= N; ++i) {
		S[i] += S[i - 1];
		std::cout << S[i] << '\n';
	}
	return 0;
}

AGC003F

首先若初始矩阵中的黑格不和外面接上,答案就是 \(B^{k-1}\),其中 \(B\) 是初始方针黑格个数。这个是显然的。

其次,假如这个矩阵和自己的两个方向都联通,那么答案为 \(1\). 这一步并不是很好证明,我们考虑归纳。

步骤 \(1\):假如对于当前矩阵,自己和上边和右边联通,那么我们可以取右上的轮廓线,通过连通性加点,所以这个矩阵所构造的下一个矩阵黑格连通块个数仍为 \(1\).

步骤 \(2\):我们发现这个矩阵仍满足上边和右边联通。以上边为例,我们可以取上一个矩阵中最上一行和最下一行共有的黑格子,在这个版本里也一定挨着。

通过步骤 \(1\)\(2\) 即可完成证明。


现在只需要讨论左右连通 / 上下连通两种情况之一了,不妨讨论前者,后者类似。

我们定义 \(1\) 级分形为一个点,假如 \(1\) 级分形相邻就连一条边。那么根据 \(2\) 级分形的定义,可以发现按层来说,图实际上是若干条链。(大概就是下面这样

image.png

然后归纳一下,发现对于任意级分形,图形态都是若干条链。

那么就好搞了:连通块个数在森林情况下有等式 \(C = V - E\),我们套用下定义就可以发现 \(V\)\(k-1\) 阶分形的黑块个数,\(E\)\(k-1\) 阶分形的水平相邻黑块对数。

\(V\) 的搞法是 trivial 的,问题变成怎么搞 \(E\). 定义第 \(k\) 个版本的 \(E\)\(E_k\).

首先对于整块的情况,\(E_k\) 可以直接加上 \(E_{k-1} \times B_k\),原因较显然。接下来问题变为怎么看边界有多少对这样的东西,发现拼起来只有可能是两个黑格搞一起,也是可以被表示成矩阵形式的。所以矩阵快速幂就完了。总复杂度为 \(O(HW + \log k)\).

posted @ 2021-05-30 09:15  LiM_233  阅读(78)  评论(0)    收藏  举报