比赛链接
官方题解
Problem A. Connection and Disconnection
令最终字符串中每一段连续的字符长度为 ,答案显然是 。
进行简单分类讨论即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int m, len[MAXN];
char s[MAXN], t[MAXN];
int main() {
scanf("%s", s + 1);
int n; n = strlen(s + 1);
ll ans = 0, k; read(k);
for (int i = 1; i <= n; i++)
if (s[i] == t[m]) len[m]++;
else t[++m] = s[i], len[m] = 1;
if (m == 1) ans = n * k / 2;
else if (t[1] == t[m]) {
ans += len[1] / 2;
ans += len[m] / 2;
ans += (len[1] + len[m]) / 2 * (k - 1);
for (int i = 2; i <= m - 1; i++)
ans += len[i] / 2 * k;
} else {
for (int i = 1; i <= m; i++)
ans += len[i] / 2 * k;
}
writeln(ans);
return 0;
}
Problem B. Graph Partition
首先考虑无解的问题,不难发现当且仅当图是二分图,问题有解。
判断有解后,考虑枚举 ,并从 的所有点出发进行 BFS ,各个点对应的距离 即为它们被分入的组。
并且,可以发现我们可以假设 中只有一个点,而答案不会变劣,这是因为我们完全可以将 中其余的点划分入 。因此,我们只需要枚举 个 即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN][MAXN];
int n, dist[MAXN];
bool vis[MAXN], col[MAXN];
void bfs(int pos) {
static int q[MAXN];
int l = 0, r = 0; q[0] = pos;
memset(dist, 0, sizeof(dist));
dist[pos] = 1;
while (l <= r) {
int pos = q[l++];
for (int i = 1; i <= n; i++)
if (s[pos][i] == '1' && dist[i] == 0) {
dist[i] = dist[pos] + 1;
q[++r] = i;
}
}
}
void dfs(int pos) {
vis[pos] = true;
for (int i = 1; i <= n; i++)
if (s[pos][i] == '1') {
if (vis[i]) {
if (col[i] == col[pos]) {
puts("-1");
exit(0);
}
} else {
col[i] = !col[pos];
dfs(i);
}
}
}
int main() {
read(n);
for (int i = 1; i <= n; i++)
scanf("\n%s", s[i] + 1);
dfs(1);
int ans = 0;
for (int i = 1; i <= n; i++) {
bfs(i);
for (int j = 1; j <= n; j++)
chkmax(ans, dist[j]);
}
writeln(ans);
return 0;
}
Problem C. Division by Two with Something
在二进制表示下考虑题目中的操作,即删去数字的最低位,并将相反的数接在最高位之前。
那么,不难发现在 次操作后,数字就会恢复原状。
进一步地,在 次操作后,数字会恢复原状,当且仅当 是 的因数, 是奇数,并且若将数字每 位分为一组,相邻的组对应位置的数字恰好相反。
则可以用容斥原理计算答案,从大到小枚举 ,计算此类数字已经被计算的贡献,用目标贡献减之,作为新贡献计入答案。
时间复杂度 ,其中 表示 的因数个数。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN], t[MAXN];
bool vis[MAXN];
int n, ans, val[MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int cnt(int x) {
int ans = 0;
for (int i = 1; i <= x; i++)
ans = ((ans * 2) + (s[i] - '0')) % P;
for (int i = 1, j = 1; i <= n; i++, j = (j == 2 * x) ? 1 : (j + 1))
if (j <= x) t[i] = s[j];
else t[i] = s[j - x] ^ 1;
for (int i = 1; i <= n; i++) {
if (s[i] > t[i]) return ans + 1;
if (t[i] > s[i]) return ans;
}
return ans + 1;
}
int main() {
read(n), scanf("%s", s + 1);
for (int i = 1; i <= n; i++)
if (n % i == 0 && (n / i) % 2 == 1) vis[i] = true;
int ans = 1ll * cnt(n) * (2 * n) % P;
for (int i = n - 1; i >= 1; i--) {
if (!vis[i]) continue;
int now = 2 * n;
for (int j = i; j <= n; j += i)
if (vis[j]) update(now, val[j]);
val[i] = 2 * i;
update(val[i], P - now);
update(ans, 1ll * cnt(i) * val[i] % P);
}
writeln(ans);
return 0;
}
Problem D. Incenters
"I prefer solving problems about world history rather than MO geometry."
对于选定的三个点 ,令 为不经过 的圆弧 的中点, 同理。
可以证明, 的内心与 的重心重合。
而在 中,考虑三角形的 欧拉线 ,令外心为 ,中心为 ,重心为 ,则 在 的延长线上,并且 。原题中,显然 ,因此若能计算出 坐标的期望,便能计算出 坐标的期望
显然 ,因此只需要枚举两个点计算 的贡献即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const double pi = acos(-1);
const double eps = 1e-10;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
double a[MAXN];
int main() {
int n, l; read(n), read(l);
for (int i = 1; i <= n; i++)
read(a[i]);
double ansx = 0, ansy = 0;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++) {
ansx += cos(pi * (a[i] + a[j]) / l) * (n - (j - i + 1));
ansy += sin(pi * (a[i] + a[j]) / l) * (n - (j - i + 1));
ansx += cos(pi * (a[i] + a[j] + l) / l) * (j - i - 1);
ansy += sin(pi * (a[i] + a[j] + l) / l) * (j - i - 1);
}
double cnt = 1.0 * n * (n - 1) * (n - 2) / 6;
printf("%.10lf %.10lf\n", ansx / cnt, ansy / cnt);
return 0;
}
Problem E. Pairing Points
首先考虑与 配对的点,不妨令为点 。
那么,我们需要对 继续配对,并且,横跨两个区间的边至少要有一条,若有多条,则需要满足它们的端点之间存在单调性,即保证连线不成环。
考虑枚举最靠外侧的横跨两个区间的边 。
之间的点或是与 连通,或是与 连通,并且存在一个分界线,考虑枚举它,记为 ,同理,枚举与 或 连通的分界线 。
由于 是最靠外侧的横跨两个区间的边,因此,对于区间 内的点,我们不再需要知晓区间 内的信息,从而递归为了一个点集 的子问题。同理, 区间 内的点递归为了一个点集 的子问题,而剩余的点则递归为了一个点集 的子问题。
由此进行动态规划,时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 55;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN][MAXN];
ll dp[MAXN][MAXN][MAXN];
ll getans(int l, int r, int m) {
if (dp[l][r][m] != -1) return dp[l][r][m];
if (l == r) return dp[l][r][m] = 1;
if (l == m || r == m) return dp[l][r][m] = 0;
ll &res = dp[l][r][m]; res = 0;
for (int i = l; i <= m - 1; i++)
for (int j = m + 1; j <= r; j++) {
if (s[i][j] == '0') continue;
for (int s = i; s <= m - 1; s++)
for (int t = m + 1; t <= j; t++)
res += getans(l, s, i) * getans(s + 1, t - 1, m) * getans(t, r, j);
}
return res;
}
int main() {
int n; read(n);
for (int i = 1; i <= n * 2; i++)
scanf("\n%s", s[i] + 1);
memset(dp, -1, sizeof(dp));
ll ans = 0;
for (int i = 2; i <= n * 2; i++)
if (s[1][i] == '1') ans += getans(2, n * 2, i);
writeln(ans);
return 0;
}
Problem F. Min Product Sum
考虑一个矩阵权值的组合意义,可以认为除了矩阵 外,我们还填写了矩阵 ,并且,矩阵 各行各列的最大值不超过矩阵 对应行列的最小值,求总方案数。
令矩阵 各行的最大值为 ,矩阵 各列的最小值为 。
我们希望得到一个动态规划解法,而状态实际上已经确定,应当为已经确定的行数 、列数 、数字数 ,即状态数是 级别的。因此,我们需要一个线性的转移,但是,若状态 都是用来描述矩阵 的,在转移时,我们就不得不枚举两维进行转移,从而超过了需要的线性复杂度。
由此,可以想到用其中一个状态描述 ,另一个状态描述 。
记 表示已经考虑了 的 行和 的 列,此时已经确定的数的填法总数。
转移时首先考虑枚举 的行数,对于每一个这样的行,可以在已经确定 的 列填上 的 中的元素,并在尚未确定 的 列填上 的 中的元素,并保证至少有一个填入的数 。
再考虑枚举 的列数,对于每一个这样的列,可以在已经确定 的 行填上 的 中的元素,并保证至少有一个填入的数 ,并在尚未确定 的 行填上 的 中的元素。
不难发现上述两个转移可以认为是转移的两个阶段,因此,只需要给状态加一维 即可。
预处理转移系数,时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
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;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, m, k, P, dp[MAXN][MAXN][MAXN][2];
int binom[MAXN][MAXN], coef[MAXN][MAXN][MAXN][2];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
int main() {
read(n), read(m), read(k), read(P), dp[1][0][0][0] = 1;
for (int i = 0; i <= max(n, m); i++) {
binom[i][0] = 1;
for (int j = 1; j <= i; j++)
binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % P;
}
for (int t = 1; t <= k; t++)
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
int tmp = (power(t, m - i) - power(t - 1, m - i) + P) % P;
tmp = 1ll * tmp * power(k - t + 1, i) % P;
coef[t][i][j][0] = power(tmp, j);
}
}
for (int t = 1; t <= k; t++)
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
int tmp = (power(k - t + 1, i) - power(k - t, i) + P) % P;
tmp = 1ll * tmp * power(t, n - i) % P;
coef[t][i][j][1] = power(tmp, j);
}
}
for (int t = 1; t <= k; t++) {
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++) {
int tmp = dp[t][i][j][0];
for (int k = 0; i + k <= n; k++)
update(dp[t][i + k][j][1], 1ll * tmp * binom[n - i][k] % P * coef[t][j][k][0] % P);
tmp = dp[t][i][j][1];
for (int k = 0; j + k <= m; k++)
update(dp[t + 1][i][j + k][0], 1ll * tmp * binom[m - j][k] % P * coef[t][i][k][1] % P);
}
}
writeln(dp[k + 1][n][m][0]);
return 0;
}