首页 > 其他 > 详细

AGC

时间:2021-03-06 23:42:08      阅读:37      评论:0      收藏:0      [点我收藏+]

记一下自己不会的题/想了一段时间的题吧。

AGC001

AGC001D Arrays and Palindrome

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

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

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

技术分享图片

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

也可以“建头”:

技术分享图片

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

那由于最多只有 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\) 过程中。我们发现一个关键性质,就是每一个颜色肯定是先出现白色球,再出现有颜色的,也就是说我们可以对于每一个颜色分两类考虑。

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

技术分享图片

其中一个颜色的位置要一起决策。所以你就设 \(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

首先特判掉整个黑格子连通块都不连接边界的情况,显然可以直接快速幂。

AGC

原文:https://www.cnblogs.com/LiM-233/p/14492643.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!