ARC080 VP 记录

更好的阅读体验

CSP 前和 grass8woc 和 black_trees 一起 VP 了一场 ARC,进行了一个思路的打开。(CSP 游记传送门grass8woc 的 VP 记录black_trees 的 VP 记录,好像只有 black_trees 的记录里有题意)

发现远古场的 ARC 竟然是和 ABC 一起开始的,就跟 Div.1 + Div.2 差不多。

第一次做出来 ARC 的前三题耶!虽然是远古场。应该是和 grass8woc 一起打如有神助哈哈哈。

不得不说确实比板子有意思,练了一堆板子结果考场一个没考。

比赛链接

C. 4-adjacent

显然 \(2\) 以外的因子都没用,而且大于 \(2\) 的指数也没用,所以直接转化成只有 \(1, 2, 4\)

然后如果没有 \(2\) 那显然就是 \(1, 4, 1, 4, \cdots , 1, 4, 1\) 这样子。如果有 \(2\) 那么肯定是把 \(2\) 放在一堆(因为他们不能和 \(1\) 放在一起,所以尽量不要浪费 \(4\)),即形如 \(1, 4, 1, 4, \cdots 1, 4, 2, 2, \cdots 2\)

所以结论就是

puts((cnt[2] ? (cnt[1] <= cnt[4] + 1) : (cnt[1] <= cnt[4])) ? "Yes" : "No");
代码
#include <cstdio>
#include <algorithm>

const int N = 1e5 + 5;

int n;
int a[N];

int main() {
	scanf("%d", &n);
	int cnt1 = 0, cnt2 = 0, cnt4 = 0;
	for(int i = 1; i <= n; i++) {
		int x;
		scanf("%d", &x);
		if(x % 4 == 0) cnt4++;
		else if(x % 2 == 0) cnt2++;
		else cnt1++;
	}
	puts((cnt1 > cnt4 && cnt2) || (cnt1 > cnt4 + 1) ? "No" : "Yes");
	return 0;
}

D. Grid Coloring

考虑一个颜色一个颜色地放,显然这样干的前提我们需要找到一个铺满网格的四连通路径。显然这是可以的。然后就做完了。

代码
#include <cstdio>
#include <algorithm>

const int N = 100 + 5;

int n, m, K;
int a[N * N];

int b[N][N];

int main() {
	scanf("%d%d%d", &n, &m, &K);
	for(int i = 1; i <= K; i++) scanf("%d", &a[i]);
	int x = 1, y = 1, c = 1;
	while(x <= n) {
		while(c <= K && a[c] == 0) c++;
		a[c]--, b[x][y] = c;
		y += (x & 1) ? 1 : -1;
		if(y < 1) x++, y = 1;
		if(y > m) x++, y = m;
	}
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) printf("%d ", b[i][j]);
		puts("");
	}
	return 0;
}

E. Young Maids

由于要求的是字典序最小,所以我们倒着考虑。

对于一个数组(e.g. 3 2 4 1),我们考虑最后两个被删除的数的下标(因为答案是先删 2 4 再删 3 1,所以最后两个被删的数是 3 1,所以下标是 1 4),设为 \(i, j\)。那么 \(i,j\) 需要满足以下条件:

  • \(i < j\)
  • \(j - i - 1\) 是偶数
  • \(i - 1\) 是偶数
  • \(n - j\) 是偶数

换句话说,就是:

  • \(i < j\)
  • \(i\) 是奇数
  • \(j\) 是偶数

又因为我们需要字典序最小,所以我们肯定是每次贪心找两个数,相当于区间求 \(\min\)。那是不是对于每一个区间都要求一遍 ST 表呢?不是的,因为我们可以先将其划分成三个子区间,容易发现子区间互不影响,然后子区间的 \(\min\) 也可以用原来的数组(输入的数组)的 \(\min\) 来维护。需要注意的一点就是转化为在原数组中时有可能变成 \(i\) (在原来长度为 \(n\) 的数组中)是偶数 \(j\) 是奇数了,所以要记两个。

然后我们发现,删完两个数后,整个序列被划分成三段。所以我们可以直接递归下去,然后合并。然而这样会 T。

怎么办呢?我们发现合并的过程其实没必要,我们可以用一种类似懒惰计算的方式来避免合并。具体怎么做呢,就是记一个堆,存放一些有可能成为答案的区间。然后取出区间最后一个删的二元组字典序最小的那一个,把它加到答案里面,然后分成三段继续放进堆。

代码
#include <cstdio>
#include <algorithm>
#include <vector>
#include <set>
#include <array>

const int N = 2e5 + 5;
const int INF = 0x3f3f3f3f;

int n;
int a[N];

int lg[N];
int go[2][N][21];
int calc(int x, int y) { return (x == -1 || y == -1) ? (x == -1 ? y : x) : (a[x] < a[y] ? x : y); }
void preprocess() {
	lg[0] = -1;
	for(int i = 1; i <= n; i++) lg[i] = lg[i >> 1] + 1;
	for(int i = 1; i <= n; i++) go[i & 1][i][0] = i, go[!(i & 1)][i][0] = -1;
	for(int k = 0; k <= 1; k++)
		for(int j = 1; j <= 20; j++)
			for(int i = 1; i + (1 << j) - 1 <= n; i++)
				go[k][i][j] = calc(go[k][i][j - 1], go[k][i + (1 << (j - 1))][j - 1]);
}
int calc_min(int l, int r) {
	int k = lg[r - l + 1];
	return calc(go[l & 1][l][k], go[l & 1][r - (1 << k) + 1][k]);
}
std::vector<int> ans;
std::set<std::array<int, 4>> st;
void insert(int l, int r) {
	if(l > r) return;
	int x = calc_min(l, r - 1), y = calc_min(x + 1, r);
	st.insert({a[x], a[y], l, r});
}

int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	preprocess();
	insert(1, n);
	while(!st.empty()) {
		auto q = *st.begin();
		int l = q[2], r = q[3];
		st.erase(st.begin());
		int x = calc_min(l, r - 1), y = calc_min(x + 1, r);
		ans.push_back(a[x]), ans.push_back(a[y]);
		insert(l, x - 1), insert(x + 1, y - 1), insert(y + 1, r);
	}
	for(int v : ans) printf("%d ", v);
	return 0;
}

F. Prime Flip

好题。

我们令 \(\mathbb{P} = \{x | x \text{ is a prime}\} - \{2\}\),即奇质数。

首先我们可以异或差分,显然此后 \(1\) 的个数为偶数。然后问题转化为可以将 \(a_i\)\(a_{i + p}\) 反转。(\(p \in \mathbb P\)

容易发现对于两个数 \(i\)\(j\),如果 \(j - i \in \mathbb P\),那么我们可以将 \(a_i\)\(a_j\) 反转(废话)。而且此时 \(i\)\(j\) 的奇偶性不同,所以我们可以暴力连边,这一定是一个二分图。然后跑一个最大匹配。

然后肯定只能退而求其次,选择代价为 \(2\) 的消除方案。对于两个数 \(i, j\),如果存在一个 \(k\),使得 \(|i - k| \in \mathbb P, |j - k| \in \mathbb P\),那么 \(i\)\(k\)\(j\)\(k\) 的奇偶性都得相反。所以此时 \(i, j\) 必定同奇偶性。下面分情况讨论,证明如果 \(i, j\) 同奇偶性就必然能用 \(2\) 的代价消除。

  • 如果 \(j - i = 2\),那么先反转 \(i, i + 5\),再反转 \(j, j + 3\)。(注意到 \(i + 5 = j + 3\),所以这一位会被抵消)
  • 如果 \(j - i = 4\),那么先反转 \(i, i + 7\),再反转 \(j, j + 3\)。(注意到 \(i + 7 = j + 3\),所以这一位会被抵消)
  • 如果 \(j - i > 4\),由于哥德巴赫猜想在 \(10^7\) 以内是正确的,所以肯定存在一个 \(k\),使得 \(k - i \in \mathbb P, j - k \in \mathbb P\)。所以我们可以先反转 \(i, k\),再反转 \(k, j\)

所以我们跑完二分图匹配之后,左部点全是不可匹配的奇数,右部点全是不可匹配的偶数,所以可以内部两两匹配。

最后还有可能剩下一奇一偶(因为总数为偶数,所以一定不会出现只有奇或只有偶),那么我们可以任选一个 \(p \in \mathbb P\),先反转 \(i, i + p\),然后用和两个偶数一样的办法反转 \(i + p, j\),总代价为 \(3\)

然后就做完啦!

代码
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cassert>

const int N = 200 + 5;
const int VAL = 1e7 + 5;

int n;
int a[N], b[VAL];

bool isprm[VAL];
void sieve(int mx) {
	for(int i = 2; i <= mx; i++) isprm[i] = true;
	for(int i = 2; i <= mx; i++) if(isprm[i])
		for(int j = i + i; j <= mx; j += i) isprm[j] = false;
	isprm[2] = false;
}

std::vector<int> to[N];
int match[N], vis[N];
bool dfs(int u, int tag) {
	if(vis[u] == tag) return false;
	vis[u] = tag;
	for(int v : to[u]) if(!match[v] || dfs(match[v], tag)) { match[u] = v, match[v] = u; return true; }
	return false;
}

int main() {
	sieve(1e7 + 1);
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) { int x; scanf("%d", &x); b[x] = 1; }
	for(int i = 10000001; i >= 1; i--) b[i] = b[i] ^ b[i - 1];
	int m = 0;
	int cnt[2] = {0, 0};
	for(int i = 1; i <= 10000001; i++) if(b[i] == 1) a[++m] = i, cnt[i & 1]++;
	// for(int i = 1; i <= m; i++) printf("%d ", a[i]);
	for(int i = 1; i <= m; i++) for(int j = i + 1; j <= m; j++) if(isprm[a[j] - a[i]]) to[i].push_back(j), to[j].push_back(i);
	int ans = 0;
	for(int i = 1; i <= m; i++) if((a[i] & 1) && dfs(i, i)) ans++;
	cnt[0] -= ans, cnt[1] -= ans;
	ans += cnt[0] / 2 * 2 + cnt[1] / 2 * 2;
	if(cnt[0] & 1) assert(cnt[1] & 1), ans += 3;
	printf("%d\n", ans);
	return 0;
}
posted @ 2022-10-30 13:14  XxEray  阅读(48)  评论(0编辑  收藏  举报