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;
}