skicean's practice contest (1)

这场比赛是我自己找的我没做过的题,我给我自己出的比赛(我可以随便更改总时间。。)。
这可能是最后一场skicean的practice contest了,因为skicean觉得把一些题合起来出成一场不限时的contest没有意义。

比赛链接

A - Painting Machines

这是一道有思维含量的计数题

题目叙述

\(n\) 个排成一行的格子。前 \(n-1\) 个位置有机器人。每个机器人会涂向后两个格子。每个机器人都运作一遍算是一种涂法,它的贡献为最后一个涂上的格子是第几次涂的。

题解

很容易想到,如果得到贡献为 \(i\) 的排列方法数量,就解决了。但是这并不好得到。所以考虑求 \(f_i\) 表示 \(i\) 步已经涂完的方案数量。这样差分一下就可以得到贡献为 \(i\) 的排列方法数量了。

首先,必然第一个机器人。如果得到一共有多少个选择机器人的方案,那么乘上排列机器人与剩下机器人的排列方案数就是答案了。所以问题转化为如何得到一共有多少个选择机器人的方案,使得它可以覆盖所有的格子。考虑将所有机器人按照起始位置排序,那么考虑从小到大将每个机器人逐个添加。添加过程中,每个机器人至少能多涂一个格子,至多涂两个格子。如果确定每一次添加机器人能多多少个格子,那么可以唯一对应一种每个位置的机器人选不选的方案。如果我们知道一共涂了多少次就涂完了,那么就可以求出一共有多少次添加机器人能够多1个格子,有多少次能够多2个格子。所以设有 \(x\) 个多一个格子的,\(y\) 个多两个格子的。那么方案数为 \(\binom{x+y}{x}\)。然后乘上系数就完了。

总结

关键在于找到一个合适的对应关系。一个+1,+2组成的数列可以对应一个机器人选不选的方案。

代码

#include <cstdio>
using namespace std;
const int N = 1e6 + 5, Mod = 1e9 + 7;
int ml(int x, int y) { return 1ll * x * y % Mod; }
int dc(int x, int y) { return (x - y < 0) ? (x - y + Mod) : (x - y); }
int ad(int x, int y) { return (x + y > Mod) ? (x + y - Mod) : (x + y); }
int n, f[N], fac[N], inv[N];
int ksm(int x, int y) {
	int ret = 1;
	for (; y; y >>= 1, x = ml(x, x))
		if (y & 1) ret = ml(ret, x);
	return ret;
}
void Prepare() {
	fac[0] = 1;
	for (int i = 1; i <= n; ++i) fac[i] = ml(fac[i - 1], i);
	inv[n] = ksm(fac[n], Mod - 2);
	for (int i = n - 1; i >= 0; --i) inv[i] = ml(inv[i + 1], i + 1);
}
int binom(int u, int d) {
	if (u < 0 || d < 0) return 0;
	if (u < d) return 0;
	return ml(fac[u], ml(inv[d], inv[u - d]));
}
int main() {
	scanf("%d", &n);
	Prepare();
	for (int i = 1; i <= n - 1; ++i)
		f[i] = ml(binom(i - 1, n - i - 1), ml(fac[i], fac[n - i - 1]));
	for (int i = n - 1; i >= 1; --i) f[i] = dc(f[i], f[i - 1]);
	int ans = 0;
	for (int i = 1; i <= n - 1; ++i) ans = ad(ans, ml(f[i], i));
	printf("%d\n", ans);
	return 0;
}

B - Odd-Even Subsequence

题目叙述

给你一个数列,求所有长度为 \(k\) 的子数列(以 \(a\) 为例)的 \(\min\{\max\{a_1,a_3,\cdots\},\max\{ a_2,_3,\cdots\}\}\) 的最小值。

题解

考虑二分 \(\min\{\max\{a_1,a_3,\cdots\},\max\{ a_2,a_3,\cdots\}\}\le x\) 是否可行。

那么只要在所有奇数项和偶数项中有一个的最大值\(\le x\)就可以了。把序列转化为 \(0/1\) 组成的序列,如果 \(a_i\le x\) 那么对应 \(1\) ,否则是 \(0\) 。那就看是否可以只选择 \(1\) 的,并且能构成奇数项/偶数项。那么维护每一个位置向后的第一个0/1所在的位置,贪心选就行了。

总结

没啥。写代码的时候注意写对,不要为了简洁而使用玄妙写法。

代码

#include <cstdio>
using namespace std;
const int N = 2e5 + 5;
int n, k, seq[N], odd, even, nxt[N];
bool ho[N];
bool source(int lp, int rp, int need) {
	if (rp < lp) return 0;
	int now = rp + 1;
	for (int i = rp; i >= lp; --i) {
		if (ho[i]) now = i;
		nxt[i] = now;
	}
	now = nxt[lp];
	if (now == rp + 1) return 0;
	int cnt = 0;
	while (now <= rp) {
		++cnt;
		if (now <= rp - 2)
			now = nxt[now + 2];
		else
			break ;
	}
	return cnt >= need;
}
bool checkOdd() {
	int bg = 1, ed = (k & 1) ? n : (n - 1);
	return source(bg, ed, odd);
}
bool checkEven() {
	int bg = 2, ed = (k & 1) ? (n - 1) : n;
	return source(bg, ed, even);
}
bool check(int ans) {
	for (int i = 1; i <= n; ++i) ho[i] = (seq[i] <= ans);
	return checkOdd() | checkEven();
}
int main() {
	scanf("%d%d", &n, &k);
	if (k & 1) {
		odd = (k + 1) >> 1;
		even = k >> 1;
	} else {
		odd = k >> 1;
		even = k >> 1;
	}
	for (int i = 1; i <= n; ++i) scanf("%d", &seq[i]);
	int L = 1, R = 1e9, ans = 1e9;
	while (L <= R) {
		int mid = (L + R) >> 1;
		if (check(mid)) {
			R = mid - 1;
			ans = mid;
		} else
			L = mid + 1;
	}
//	printf("check(1) : %d\n", check(1));
	printf("%d\n", ans);
	return 0;
}
/*
9 6
61893 41300 6953 17157 3356 96839 77399 31252 37704
*/

C - Binary Subsequence Rotation

题目叙述

现在有两个长度相同01组成的序列,每次可以选择第一个序列的一个子序列让他们变成下一个轮换。比如11001轮换就是10011\(a_i\rightarrow a_{i+1}\)\(a_n\rightarrow a_1\))。问最少多少次可以将第一个序列变成第二个。

题解

可以发现如果选择的子序列存在相邻两个位置上的数相同,那么这次轮换会有一些位置并没有改变。所以可以考虑将靠后的那个位置去掉(不参与这次变换),这样选择的子序列必然是一个相邻不同的子序列。那么一次变换就是一次等价于选择一些不同相邻不同的(并且0和1的总数量一样)的子序列每个位置取反。需要取反的只有这两个序列的不同值得位置,所以把相同的去掉即可。

所以有一个想法是答案$\ge $最长连续相同的长度(其实这个想法和 这题 比较像)。但是这样你会发现这样会wa on test 10。仔细一想,这实际上是一个错误算法,反例...挺好造的。那么怎么办呢?考虑贪心。每次选择一个位置,每回选择下一个还没有变成第二个序列、与当前位置值不相同的位置,然后将这些选上的位置操作一下(这些位置的值就变正确了),++ans

那么问题就是如何进行这些操作。考虑维护两个set,每个维护值为0的位置集合与值为1的位置集合(并且这些位置的数目前都不正确)。每次二分到下一个位置,然后将这个位置从set中去掉。

大概就是这样,还有一些细节。复杂度\(\mathcal O(n\log _2n)\)

总结

猜玄学结论之前要验证一下。。

代码

#include <cstdio>
#include <iostream>
#include <set>
#include <vector>
using namespace std;
typedef set<int>::iterator sit;
const int N = 1e6 + 5;
int n, m, ans;
char A[N], B[N];
int s[N];
set<int> ps[2];
bool vis[N];
int main() {
	scanf("%d%s%s", &n, A + 1, B + 1);
	for (int i = 1; i <= n; ++i)
		if (A[i] != B[i])
			s[++m] = A[i] - '0';
	for (int i = 1; i <= m; ++i)
		ps[s[i]].insert(i);
	for (int i = 1; i <= m; ++i) {
		if (vis[i]) continue ;
		++ans;
		int now = i, lst = 0, cnt = 0;
		sit tmp;
		while(1) {
			++cnt;
			if ((tmp = ps[s[now] ^ 1].upper_bound(now)) == ps[s[now] ^ 1].end() && (cnt & 1)) {
				--cnt;
				break ;
			}
			ps[s[now]].erase(now);
			lst = now;
			vis[now] = 1;
			if (tmp == ps[s[now] ^ 1].end())
				break ;
			now = *tmp;
		}
	}
	for (int i = 1; i <= m; ++i)
		if (!vis[i]) {
			printf("-1\n");
			return 0;
		}
	printf("%d\n", ans);
	return 0;
}

D - TediousLee

题目叙述

详见题面。懒于描述。。。

题解

\(f_i=2f_{i-2}+f_{i-1}+[i\mod 3=0]\cdot 4\)

肯定是从下向上选claw最优。所以就是看 \(i\) 阶的最考上的claw是否能选,容易发现 \(i\mod 3=0\) 时可选。

总结

多组询问可以直接把答案统一处理出来。。。

代码

#include <cstdio>
using namespace std;
const int N = 2e6 + 5, Mod = 1e9 + 7;
int ml(int x, int y) { return 1ll * x * y % Mod; }
int ad(int x, int y) { return (x + y > Mod) ? (x + y - Mod) : (x + y); }
int t, f[N];
int main() {
	for (int i = 1; i <= N - 5; ++i)
		f[i] = ad(ml(2, f[i - 2]), ad(f[i - 1], (i % 3 == 0) ? 1 : 0));
	scanf("%d", &t);
	while (t--) {
		int l;
		scanf("%d", &l);
		printf("%d\n", ml(f[l], 4));
	}
	return 0;
}
posted @ 2020-07-03 22:47  acniu  阅读(200)  评论(0编辑  收藏  举报