盖世计划--0806--B班训练
A
B
神秘题
根据 2008 年集训队论文可以给出 \(O(n)\) 做法,不会。
考虑从 \(k\) 小的情况出发。
当 \(k=1\) 时,结论:当且仅当 \(n\) 为 \(2\) 的幂次时,先手必败。可以通过二进制构造方案求解。
当 \(k=2\) 时,结论:当且仅当 \(n\) 为斐波那契数时先手必败。将每个数通过斐波那契数分解,用 \(k=1\) 的方法证明。
否则,我们希望能够将 \(n\) 通过某个神秘序列分解,使得用 \(k=1\) 的方法先手能够必胜。
这个神秘序列满足:
- 可以将 \(1-n\) 之内的数分解为若干序列中的数,使得排序后相邻数至少为 \(k\) 倍关系。
然后大力构造。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define mk std::make_pair
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
/*
*/
const int N = 2e7 + 10;
int a[N], b[N];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
a[0] = b[0] = 1;
int i = 0, j = 0;
while(a[i] < n) {
i++;
a[i] = b[i - 1] + 1;
while(a[j] * k < a[i]) j++;
j--;
if(a[j] * k < a[i]) b[i] = b[j] + a[i];
else b[i] = a[i];
}
if(a[i] == n) {
std::cout << "lose\n";
return 0;
}
i--;
while(n) {
if(n == a[i]) break;
if(n > a[i]) n -= a[i];
i--;
}
std::cout << a[i] << "\n";
return 0;
}
C
博弈论
每个点都有必胜或必败状态,可以线性求出。
如果原本 \(\texttt{Alice}\) 就能赢,那么代价为 \(0\)。
否则加边。首先加边不能构成环,不然会平局。
然后起点在加边后能够到达,终点的状态一定是先手必败。
总结一下,合法的加边满足:
- 不连向祖先
- 起点在加边后能够到达
- 终点的状态一定是先手必败
2 情况的解决方法是模拟博弈的过程。假如已知起点,那么终点就是不包含到根路径的,所有状态为必败的,节点的权值最小值。两遍 bfs 解决,一次从左到右,一次从右到左,回溯时更新最小值。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define mk std::make_pair
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
/*
*/
const int N = 1e6 + 10;
i64 T, n, t, A, B, d, ans, mn;
i64 a[N];
i64 f[N], g[N];
std::vector<int> e[N];
i64 mnl[N], mnr[N];
i64 read() {
i64 x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
x = (x << 1) + (x << 3) + (c - '0');
c = getchar();
}
return x * f;
}
i64 dfs1(int u) {
f[u] = g[u] = 0;
mnl[u] = linf;
for(int v : e[u]) {
mn = std::min(mn, dfs1(v));
f[u] |= !f[v];
g[u] += !f[v];
}
mnl[u] = mn;
return std::min(mn, !f[u] ? a[u] : linf);
}
i64 dfs2(int u) {
mnr[u] = linf;
for(int i = e[u].size() - 1; i >= 0; i--) {
int v = e[u][i];
mn = std::min(mn, dfs2(v));
}
mnr[u] = mn;
return std::min(mn, !f[u] ? a[u] : linf);
}
void imit(int u, int now) {
if(!now) {
if(std::min(mnl[u], mnr[u]) != linf) ans = std::min(ans, A * a[u] + B * std::min(mnl[u], mnr[u]));
}
for(int v : e[u]) {
if((g[u] == 1 && !f[v]) || !now) {
imit(v, now ^ 1);
}
}
}
void fake_main() {
d = 0, ans = linf;
n = read(), t = read(), A = read(), B = read();
for(int i = 2; i <= n; i++) {
int fa = read();
e[fa].pb(i);
}
for(int i = 1; i <= n; i++) a[i] = read();
mn = linf, dfs1(1);
mn = linf, dfs2(1);
if((!t && f[1]) || (t && !f[1])) {
printf("0\n");
for(int i = 1; i <= n; i++) e[i].clear();
return;
}
imit(1, t);
printf("%lld\n", (ans != linf) ? ans : -1);
for(int i = 1; i <= n; i++) e[i].clear();
}
int main() {
// T = read();
T = 1;
while(T--) fake_main();
return 0;
}
D
计数题,首先不考虑第二个限制,可以设 \(g_{i,j}\) 求出 \(i\) 个球,\(j\) 种颜色的方案数。
然后设 \(f_{i,j}\) 表示前 \(i\) 层,第 \(i\) 层 \(j\) 种颜色的方案数。
预处理排列数和阶乘就做完了。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define mk std::make_pair
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
/*
*/
const int N = 1e6 + 10, M = 5e3 + 10;
i64 n, m, p, maxn, sum;
i64 l[N];
i64 A[M], fac[M], g[M][M], f[2][M];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m >> p;
for (int i = 1; i <= n; i++) std::cin >> l[i], maxn = std::max(maxn, l[i]);
A[0] = 1;
for(int i = 1; i <= maxn; i++) A[i] = A[i - 1] * (m - i + 1) % p;
fac[0] = 1;
for(int i = 1; i <= maxn; i++) fac[i] = fac[i - 1] * i % p;
g[0][0] = 1;
for(int i = 1; i <= maxn; i++) {
for(i64 j = 1; j <= maxn; j++) {
g[i][j] = (g[i - 1][j] * (j - 1) % p + g[i - 1][j - 1]) % p;
}
}
int lst = 1, cur = 0;
sum = 1;
for(int i = 1; i <= n; i++) {
std::swap(lst, cur);
for(int j = 1; j <= std::min(m, l[i]); j++) {
f[cur][j] = (A[j] * g[l[i]][j] % p * sum % p - fac[j] * g[l[i]][j] % p * f[lst][j] % p + p) % p;
}
sum = 0;
for(int j = 1; j <= std::min(m, l[i]); j++) sum = (sum + f[cur][j]) % p;
for(int j = 1; j <= l[i - 1]; j++) f[lst][j] = 0;
}
std::cout << sum << "\n";
return 0;
}
E
不合法的数只有一种,考虑贪心地,把不合法的数尽可能多的并到合法的数中,假设不合法的数为 \(s\),总数为 \(n\),那么最多能并 \(n-s+1\) 个,剩下的不合法的分为一个一个。
考虑到摩尔投票法,用它和线段树维护区间绝对众数。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define mk std::make_pair
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
/*
*/
const int N = 1e6 + 10;
int n, q;
struct seg {
int v, c;
} t[N << 2];
std::vector<int> v[N];
int a[N];
void pushup(seg &u, seg ls, seg rs) {
if(ls.v == rs.v) {
u.v = ls.v, u.c = ls.c + rs.c;
} else if(ls.c < rs.c) {
u.v = rs.v, u.c = rs.c - ls.c;
} else {
u.v = ls.v, u.c = ls.c - rs.c;
}
}
void build(int u, int l, int r) {
if(l == r) {
t[u] = {a[l], 1};
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(t[u], t[u << 1], t[u << 1 | 1]);
}
seg qry(int u, int l, int r, int L, int R) {
if(L <= l && r <= R) return t[u];
int mid = (l + r) >> 1;
if(R <= mid) return qry(u << 1, l, mid, L, R);
if(L > mid) return qry(u << 1 | 1, mid + 1, r, L, R);
seg ret = {0, 0}, ls = qry(u << 1, l, mid, L, R), rs = qry(u << 1 | 1, mid + 1, r, L, R);
pushup(ret, ls, rs);
return ret;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> q;
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
v[a[i]].pb(i);
}
build(1, 1, n);
while(q--) {
int l, r;
std::cin >> l >> r;
int x = qry(1, 1, n, l, r).v;
int sum = std::upper_bound(v[x].begin(), v[x].end(), r) - 1 - std::lower_bound(v[x].begin(), v[x].end(), l) + 1;
if(sum <= (r - l + 2) / 2) std::cout << "1\n";
else {
std::cout << 1 + (sum - (r - l + 1 - sum + 1)) << "\n";
}
}
return 0;
}
F
鸽