比赛链接
官方题解
Problem A. Table Tennis Training
若 奇偶性相同,则答案为 。
否则,答案为 。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int main() {
ll n, a, b; read(n), read(a), read(b);
if ((b - a) % 2 == 0) cout << (b - a) / 2 << endl;
else cout << min(a + b - 1, 2 * n - a - b + 1) / 2 << endl;
return 0;
}
Problem B. Voting Judges
二分答案,考虑如何判断某个题是否能够能够出线。
问题可以规约为如下新问题:
给定数组
,两个整数
。
可以进行
次操作,每次选择
个数
,问是否能够使得
。
考虑每次操作最大的 个 ,可以发现可以满足条件当且仅当 。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, v, p, a[MAXN];
bool check(int pos) {
int kill = n - p; ll sum = 0;
for (int i = n; i >= 1 && kill; i--)
if (i != pos) {
if (a[pos] - a[i] > m) return false;
sum += max(a[pos] - a[i], 0), kill--;
}
return sum <= 1ll * m * v;
}
int main() {
read(n), read(m), read(v), read(p), v = n - v;
for (int i = 1; i <= n; i++)
read(a[i]), a[i] = 1e9 - a[i];
sort(a + 1, a + n + 1);
int l = 1, r = n;
while (l < r) {
int mid = (l + r + 1) / 2;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
return 0;
}
Problem C. Domino Quality
时可以简单构造,且可以通过重复这个构造解决 是 的倍数的情况。
是 的偶数时,可以简单构造,使得各行各列的权值均为 。
构造 时的解,对于其余是奇数的 ,可以拼一个 的构造使得 变为偶数。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e3 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
char s[MAXN][MAXN];
int main() {
int n; read(n);
if (n == 2) {
puts("-1");
return 0;
}
if (n == 5) {
puts("aabbc");
puts("bb.ec");
puts("..fed");
puts("c.f.d");
puts("cbbaa");
return 0;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
s[i][j] = '.';
if (n % 3 == 0) {
for (int i = 1, j = 1; i <= n / 3; i++, j += 3) {
s[j + 0][j + 0] = s[j + 0][j + 1] = 'a';
s[j + 1][j + 2] = s[j + 2][j + 2] = 'a';
}
} else {
int tn = n;
if (n & 1) {
int m = n - 7; tn -= 7;
s[m + 1][m + 1] = s[m + 1][m + 2] = 'a';
s[m + 1][m + 3] = s[m + 1][m + 4] = 'b';
s[m + 1][m + 5] = s[m + 1][m + 6] = 'c';
s[m + 2][m + 1] = s[m + 2][m + 2] = 'd';
s[m + 2][m + 3] = s[m + 2][m + 4] = 'e';
s[m + 3][m + 1] = s[m + 3][m + 2] = 'f';
s[m + 3][m + 3] = s[m + 3][m + 4] = 'g';
s[m + 2][m + 7] = s[m + 3][m + 7] = 'a';
s[m + 4][m + 7] = s[m + 5][m + 7] = 'b';
s[m + 6][m + 7] = s[m + 7][m + 7] = 'c';
s[m + 4][m + 6] = s[m + 5][m + 6] = 'd';
s[m + 6][m + 6] = s[m + 7][m + 6] = 'e';
s[m + 4][m + 5] = s[m + 5][m + 5] = 'f';
s[m + 6][m + 5] = s[m + 7][m + 5] = 'g';
}
for (int i = 1, j = 1; i <= tn / 2; i++, j += 2) {
s[j + 0][j + 0] = s[j + 0][j + 1] = 'a';
s[j + 1][j + 0] = s[j + 1][j + 1] = 'b';
if (i != tn / 2) {
s[j + 2][j + 0] = s[j + 3][j + 0] = 'c';
s[j + 2][j + 1] = s[j + 3][j + 1] = 'd';
} else {
s[1][j + 0] = s[2][j + 0] = 'c';
s[1][j + 1] = s[2][j + 1] = 'd';
}
}
}
for (int i = 1; i <= n; i++)
printf("%s\n", s[i] + 1);
return 0;
}
Problem D. Problem Scores
题目中给出的第
个限制为
注意到对于第 个限制,在两边减去重复的元素后可以变为一个已经存在的限制,因此我们只需要考虑前 个限制。
记 表示已经考虑了前 个限制,目前 ,且 时可行的方案数,转移时枚举 即可。
由此,可以得到一个时间复杂度 的 DP 做法。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, P;
int dp[MAXN][MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
read(n), read(P);
for (int i = 1; i <= n; i++)
dp[0][i][n - i] = 1;
for (int t = 1; t <= (m = (n - 1) / 2); t++)
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n - 1; j++) {
int tmp = dp[t - 1][i][j];
if (dp[t - 1][i][j] == 0) continue;
for (int k = 0; k <= j && k <= i - 1; k++)
update(dp[t][i - k][k], 1ll * tmp * (j - k + 1) % P);
}
int ans = 0;
if (n & 1) {
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n - 1; j++)
update(ans, dp[m][i][j]);
} else {
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n - 1; j++)
update(ans, 1ll * dp[m][i][j] * (j + 1) % P);
}
cout << ans << endl;
return 0;
}
上述做法的复杂度瓶颈在于 DP 。
记 。
成为复杂度瓶颈的 DP 中,状态中的
即为
,这个 DP 计算了满足
的所有
对应的
的和。
注意到限制 不是很好处理,可以考虑枚举 ,则这个限制可以写为 。
由此,可以将 DP 的状态改为两维,即 的个数,以及 的总和,由于 是有序的,转移时只需要枚举最小的 是多少即可,转移的复杂度应当用调和级数分析。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, P;
int dp[MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
read(n), read(P), m = (n - 1) / 2;
dp[0][0] = 1;
for (int i = 1; i <= m; i++)
for (int j = 0; j <= n; j++) {
int tmp = 0;
for (int k = 0; k * i <= j; k++)
update(tmp, 1ll * dp[i - 1][j - k * i] * (k + 1) % P);
dp[i][j] = tmp;
}
int ans = 0;
if (n & 1) {
for (int i = 1; i <= n; i++)
for (int j = 0; j * (m + 1) <= n - i; j++)
update(ans, dp[m][n - i - j * (m + 1)]);
} else {
for (int i = 1; i <= n; i++)
for (int j = 0; j * (m + 1) <= n - i; j++)
update(ans, 1ll * dp[m][n - i - j * (m + 1)] * (j + 1) % P);
}
cout << ans << endl;
return 0;
}
上面的 DP 是可以进一步优化的,转移系数 可以看做在 到 中选择一个关键点的方案数,则在 DP 中增设一维 ,表示是否选取过关键点即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, P, dp[MAXN][MAXN][2];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
read(n), read(P), m = (n - 1) / 2;
dp[0][0][0] = 1;
for (int i = 1; i <= m; i++)
for (int j = 0; j <= n; j++) {
update(dp[i][j][1], dp[i - 1][j][0]);
if (j >= i) update(dp[i][j][1], dp[i][j - i][1]);
dp[i][j][0] = dp[i][j][1];
if (j >= i) update(dp[i][j][0], dp[i][j - i][0]);
}
int ans = 0;
if (n & 1) {
for (int i = 1; i <= n; i++)
for (int j = 0; j * (m + 1) <= n - i; j++)
update(ans, dp[m][n - i - j * (m + 1)][0]);
} else {
for (int i = 1; i <= n; i++)
for (int j = 0; j * (m + 1) <= n - i; j++)
update(ans, 1ll * dp[m][n - i - j * (m + 1)][0] * (j + 1) % P);
}
cout << ans << endl;
return 0;
}
Problem E. Balancing Network
对于 的问题,考虑判断是否能够将所有线路接到某条线路 。
则应当将操作序列反过来,依次考虑每个操作,若一个操作连接了一个可以到达 的线路和一个不能到达 的线路,则可以通过操作使得它们都可以到达 。
由此,我们有了一个 的做法,用 优化它即可。
对于 的问题,依然考虑则应当将操作序列反过来。
记 表示线路 最终到达的线路,则初始时显然有 。
对于操作 ,在 数组中的体现即为 。
我们希望最终 数组中至少有两种数,修改出现次数较多的一个数即可。
时间复杂度 ,其中 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
const int MAXM = 1e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
char s[MAXM]; bitset <MAXN> a[MAXN];
int n, m, t, x[MAXM], y[MAXM], b[MAXN], c[MAXN];
int main() {
read(n), read(m), read(t);
if (t == 1) {
for (int i = 1; i <= n; i++)
a[i].set(i);
for (int i = 1; i <= m; i++) {
read(x[i]), read(y[i]);
a[x[i]] = a[y[i]] = a[x[i]] | a[y[i]];
}
for (int i = 1; i <= n; i++)
if (a[i].count() == n) {
static bool vis[MAXN];
vis[i] = true;
for (int i = m; i >= 1; i--) {
if (vis[x[i]]) {
s[i] = '^';
vis[y[i]] = true;
} else if (vis[y[i]]) {
s[i] = 'v';
vis[x[i]] = true;
} else s[i] = '^';
}
printf("%s\n", s + 1);
return 0;
}
puts("-1");
} else {
if (n == 2) puts("-1");
else {
for (int i = 1; i <= n; i++)
b[i] = i, c[i] = 1;
for (int i = 1; i <= m; i++)
read(x[i]), read(y[i]);
for (int i = m; i >= 1; i--) {
if (b[x[i]] == b[y[i]]) s[i] = '^';
else if (c[b[x[i]]] >= 2) {
c[b[x[i]]]--;
b[x[i]] = b[y[i]];
c[b[x[i]]]++;
s[i] = 'v';
} else {
c[b[y[i]]]--;
b[y[i]] = b[x[i]];
c[b[y[i]]]++;
s[i] = '^';
}
}
printf("%s\n", s + 1);
}
}
return 0;
}
Problem F. Histogram Rooks
考虑给定的图通过移除最下方一行,以及断开已经没有元素的列形成的子图。
对于一个子图中的列,有如下三种可能:
、已经存在一个车
、不存在车,但各行均被某个车的攻击范围覆盖
、不存在车,且存在没有被某个车的攻击范围覆盖的格子
关键的一点是注意到若某一行下方没有不存在车的行,则 类列可以被视为 类列;否则 类列可以被视为 类列。
那么,将某一行下方没有不存在车的行决定好,作为一个 位计入状态,再在状态中计入 类列的个数就可以转移了。转移即考虑移除当前图最下方的一行,或是断开已经没有元素的列。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 405;
const int MAXM = 805;
const int P = 998244353;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, tot, num[MAXN][MAXN], ptwo[MAXN], a[MAXN];
int dp[MAXM][MAXN][MAXN][2], size[MAXM], binom[MAXN][MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int work(int l, int r, int h) {
if (l > r) {
dp[0][h][0][0] = 1;
dp[0][h][0][1] = 1;
return 0;
}
int &res = num[l][r];
if (res == 0) res = ++tot;
int Min = n, pos = l;
for (int i = l; i <= r; i++)
if (a[i] < Min) {
Min = a[i];
pos = i;
}
if (Min == h) {
int x = work(l, pos - 1, h);
int y = work(pos + 1, r, h);
size[res] = size[x] + size[y] + 1;
for (int i = 0; i <= size[x]; i++)
for (int j = 0; j <= size[y]; j++) {
update(dp[res][h][i + j + 0][0], 1ll * dp[x][h][i][0] * dp[y][h][j][0] % P);
update(dp[res][h][i + j + 1][1], 1ll * dp[x][h][i][1] * dp[y][h][j][1] % P);
}
} else {
work(l, r, h + 1);
for (int i = 0; i <= size[res]; i++) {
update(dp[res][h][i][0], dp[res][h + 1][i][1]);
update(dp[res][h][i][1], dp[res][h + 1][i][1]);
update(dp[res][h][i][0], 1ll * dp[res][h + 1][i][0] * (ptwo[size[res] - i] - 1) % P);
update(dp[res][h][i][1], 1ll * dp[res][h + 1][i][1] * (ptwo[size[res] - i] - 1) % P);
}
for (int i = 1; i <= size[res]; i++)
for (int j = 1; j <= i; j++) {
update(dp[res][h][i - j][0], 1ll * dp[res][h + 1][i][0] * ptwo[size[res] - i] % P * binom[i][j] % P);
update(dp[res][h][i - j][1], 1ll * dp[res][h + 1][i][1] * ptwo[size[res] - i] % P * binom[i][j] % P);
}
}
return res;
}
int main() {
read(n), ptwo[0] = binom[0][0] = 1;
for (int i = 1; i <= n; i++) {
read(a[i]), binom[i][0] = 1;
ptwo[i] = 2 * ptwo[i - 1] % P;
for (int j = 1; j <= i; j++)
binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % P;
}
int res = work(1, n, 0);
cout << dp[res][0][0][0] << endl;
return 0;
}