从这里开始
Problem A Connection and Disconnection
简单讨论即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = 105; #define ll long long int n, K;
char s[N];
vector<int> len; void work() {
for (int i = 1, j = 1; i <= n; i = j) {
while (j <= n && s[i] == s[j])
j++;
len.push_back(j - i);
}
} int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
scanf("%d", &K);
work();
ll ans = 0;
if ((signed) len.size() == 1) {
ans = (1ll * n * K) >> 1;
} else if (s[1] == s[n]) {
for (int i = 1; i < (signed) len.size() - 1; i++) {
ans += 1ll * (len[i] >> 1) * K;
}
ans += ((len[0] + len.back()) >> 1) * 1ll * (K - 1);
ans += len[0] >> 1;
ans += len.back() >> 1;
} else {
for (auto l : len)
ans += l >> 1;
ans = ans * K;
}
printf("%lld\n", ans);
return 0;
}
Problem B Graph Partition
如果存在奇环显然无解,否则枚举一个点作为根,然后用 bfs 生成树上的深度作为标号。
因为每个点满足它的标号小于等于它的父节点的标号 + 1,所以这样能使得答案的最大值最大。
Code
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = 205; int n;
int f[N];
char G[N][N]; int bfs(int rt) {
queue<int> Q;
memset(f, 0, sizeof(f));
f[rt] = 1;
Q.push(rt);
int ret = 1;
while (!Q.empty()) {
int p = Q.front();
Q.pop();
ret = max(ret, f[p]);
for (int e = 1; e <= n; e++) {
if (G[p][e] == '1') {
if (f[e]) {
if (f[e] == f[p])
return -1;
} else {
f[e] = f[p] + 1;
Q.push(e);
}
}
}
}
return ret;
} int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s", G[i] + 1);
}
int ans = bfs(1);
if (ans == -1) {
puts("-1");
return 0;
}
for (int i = 2; i <= n; i++) {
ans = max(ans, bfs(i));
}
printf("%d\n", ans);
return 0;
}
Problem C Division by Two with Something
不难发现一次操作相当于把数的最后一位取出来,取反,放到最前面。
考虑把 $\overline{X} X$ 作为环,答案显然等于循环节长度。
考虑可能的循环节长度 $l$,$l$ 必定满足 $l \nmid n, l \mid 2n$,剩下是非常基础的数位 dp。
Code
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;
} else {
exgcd(b, a % b, y, x);
y -= (a / b) * x;
}
} int inv(int a, int n) {
int x, y;
exgcd(a, n, x, y);
return (x < 0) ? (x + n) : (x);
} const int Mod = 998244353; template <const int Mod = :: Mod>
class Z {
public:
int v; Z() : v(0) { }
Z(int x) : v(x){ }
Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) {
int x;
return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
}
friend Z operator - (const Z& a, const Z& b) {
int x;
return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
}
friend Z operator * (const Z& a, const Z& b) {
return Z(a.v * 1ll * b.v);
}
friend Z operator ~(const Z& a) {
return inv(a.v, Mod);
}
friend Z operator - (const Z& a) {
return Z(0) - a;
}
Z& operator += (Z b) {
return *this = *this + b;
}
Z& operator -= (Z b) {
return *this = *this - b;
}
Z& operator *= (Z b) {
return *this = *this * b;
}
friend boolean operator == (const Z& a, const Z& b) {
return a.v == b.v;
}
}; Z<> qpow(Z<> a, int p) {
Z<> rt = Z<>(1), pa = a;
for ( ; p; p >>= 1, pa = pa * pa) {
if (p & 1) {
rt = rt * pa;
}
}
return rt;
} typedef Z<> Zi; const int N = 2e5 + 5; int n;
char s[N];
Zi pw2[N], F[1000]; Zi solve(int d) {
Zi rt = 0;
int hd = d >> 1;
for (int i = 0; i < hd; i++) {
if (s[i] == '1') {
rt += pw2[hd - i - 1];
}
}
int dt = 1;
for (int i = 0; i < n && dt; i++) {
int r = i % d;
char x = '0';
if (r >= hd) {
x = s[r - hd] ^ 1;
} else {
x = s[r];
}
if (x > s[i]) {
dt = 0;
} else if (x < s[i]) {
break;
}
}
return rt += dt;
} int pos = 0;
vector<int> D; int main() {
scanf("%d", &n);
scanf("%s", s);
pw2[0] = 1;
for (int i = 1; i <= n; i++)
pw2[i] = pw2[i - 1] + pw2[i - 1];
for (int i = 1; i <= n; i++) {
if (!((n << 1) % i) && (n % i)) {
F[pos++] = solve(i);
D.push_back(i);
}
}
int n2 = n << 1;
F[pos++] = solve(n2);
D.push_back(n2);
for (int i = 0; i < (signed) D.size(); i++) {
for (int j = i + 1; j < (signed) D.size(); j++) {
if (!(D[j] % D[i])) {
F[j] -= F[i];
}
}
}
Zi ans = 0;
for (int i = 0; i < (signed) D.size(); i++)
ans += F[i] * D[i];
printf("%d\n", ans.v);
return 0;
}
Problem D Incenters
不会几何,不会高中数学,不会 MO,sad...
Solution 1
假设选取的三个点依次为 $A, B, C$,取弧 $AB$ 中点 $C'$,弧 $BC$ 中点 $A'$,弧 $CA$ 中点 $B'$,连接 $A'C', A'B', B'C'$,则 $\triangle ABC$ 的内心 $I$ 是 $\triangle A'B'C'$ 的垂心 $H$,证明考虑连接 $A'A, B'B, C'C$,令 $CC'$ 交 $A'B'$ 与点 $D$,根据等弧对等角有 $\angle C'CB = \angle C'B'B, \angle BB'A = \angle A'AB, \angle B'C'C = \angle B'BC$,根据外角定理有 $\angle C'DA' = \angle DB'C' + B'C'D = \frac{1}{2} \times 180^{\circ} = 90 ^{\circ}$,所以 $CC' \perp B'A'$,同理可证 $B'B \perp A'C", AA' \perp B'C'$。
根据欧拉线有,三角形的外心 $O$,重心 $G$ 和垂心 $H$,分别位于一条直线上,并且 $OG = \frac{1}{2} HG$,证明请自行百度。
我们知道 $\triangle A'B'C'$ 的外心是原点,所以垂心坐标是重心坐标的三倍。
计算重心的坐标可以直接枚举 $AB$,计算弧 $AB$ 的贡献即可。
时间复杂度 $O(n^2)$。
Code
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = 3005; int n, L;
int a[N]; int main() {
scanf("%d%d", &n, &L);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
long double pi = acos(-1);
long double Ex = 0, Ey = 0;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int cnt1 = j - i - 1, cnt2 = n - 2 - cnt1;
long double theta = ((a[i] + a[j]) * pi / L);
Ex += cos(theta) * (cnt2 - cnt1);
Ey += sin(theta) * (cnt2 - cnt1);
}
}
long long cnt = 1ll * n * (n - 1) * (n - 2) / 6;
Ex /= cnt, Ey /= cnt;
printf("%.12lf %.12lf\n", (double) Ex, (double) Ey);
return 0;
}
Solution 2
用相似不难推出内心的坐标为每个点的坐标乘以对边长度之和再除以周长。
考虑三角形为 $\triangle ABC$,先枚举点 $A$。假设 $\vec{OA}$ 和 $\vec {OB}$ 之间的夹角为 $x$,$\vec{OC}$ 和 $\vec{OA}$ 之间的夹角为 $y$。那么有,$BC = 2\sin \frac{x + y}{2}, AB = 2\sin \frac{x}{2}, AC = 2\sin \frac{y}{2}$
$$
\begin{align}
\sin\frac{x + y}{2} + \sin \frac{x}{2} + \sin\frac{y}{2} &= \sin\frac{x}{2} \cos \frac{x}{2} + \sin \frac{y}{2} \cos \frac{x}{2} + \sin\frac{x}{2} + \sin \frac{y}{2} \\
&= \sin \frac{x}{2} \left (\cos \frac{y}{2} + 1 \right) + \sin\frac{y}{2}\left (\cos \frac{x}{2} + 1 \right) \\
&= 2\sin \frac{x}{2}\cos^2\frac{y}{4} + 2\sin\frac{y}{2}\cos^2\frac{x}{4} \\
&= 4\sin \frac{x}{4}\cos\frac{x}{4}cos^2\frac{y}{4} + 4\sin\frac{y}{4}\cos\frac{y}{4}cos^2\frac{x}{4} \\
&= 4\cos\frac{x}{4}\cos\frac{y}{4}\left (\sin\frac{x}{4}\cos\frac{y}{4} + \sin\frac{y}{4}\cos \frac{x}{4}\right ) \\
&= 4\cos \frac{x}{4} \cos\frac{y}{4} \sin \frac{x + y}{4}
\end{align}
$$
$$
\begin {align}
\frac{BC}{AB + BC + AC} &= \frac{\sin \frac{x + y}{2}}{4\cos \frac{x}{4}\cos\frac{y}{4}\sin\frac{x + y}{4}} \\
&= \frac{\sin \frac{x + y}{4}\cos\frac{x + y}{4}}{2\cos \frac{x}{4}\cos\frac{y}{4}\sin\frac{x + y}{4}} \\
&= \frac{\cos\frac{x + y}{4}}{2\cos\frac{x}{4}\cos\frac{y}{4}} \\
&= \frac{\cos\frac{x}{4}\cos\frac{y}{4} - \sin\frac{x}{4}\sin\frac{y}{4}}{2\cos\frac{x}{4}\cos\frac{y}{4}} \\
&= \frac{1}{2} \left (1 - \tan\frac{x}{4}\tan\frac{y}{4} \right)
\end {align}
$$
显然这个能够 $O(n)$ 计算。可能还需要处理一些其他的 case。
总时间复杂度也是 $O(n^2)$。
Code
咕咕咕
Problem E Pairing Points
考虑枚举和 $1$ 配对的点 $i$,根据题目的要求必然有一条线段跨过 $(1, i)$,因为不能产生环,跨过 $(1, i)$ 的线段之间互不相交。
枚举最靠 $1$ 的一条与 $(1, i)$ 相交的线段 $(x, y)$,不妨设 $x < y$。那么 $[2, x)$ 和 $(x, i)$ 之间的线段至少要有一条跨过 $x$ 的边,$(x, i)$ 和 $(i, y)$ 之间至少有一条跨过 $i$ 的边,$(i, y)$ 和 $(y, n]$ 之间至少要一条跨过 $y$ 的点。不难发现 $(x, i)$ 中间跨过 $i, x$ 存在分界线,分界线左侧和 $i$ 连通,右侧和 $x$ 连通。
枚举 $(x, i)$ 中的分界线 $p$,$(i, y)$ 中的分界线 $q$。然后问题可以变成 3 个形如将 $[l, sp) \cup (sp, r]$ 之间的点配对,必须要有 $1$ 条边跨过 $sp$。
时间复杂度 $O(n^7)$,记忆化搜索可以做到 $O(能过)$.
稍微进行一些简单的优化可以做到 $O(n^5)$。
Code
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; #define ll long long const int N = 44; int n;
char G[N][N];
ll dp[N][N][N]; ll solve(int l, int r, int sp) {
if (l == sp || r == sp)
return l == r;
ll& rt = dp[l][r][sp];
if (~rt)
return rt;
rt = 0;
for (int x = l; x < sp; x++) {
for (int y = sp + 1; y <= r; y++) {
if (G[x][y] == '0')
continue;
for (int p = x; p < sp; p++) {
for (int q = sp; q < y; q++) {
rt += solve(l, p, x) * solve(p + 1, q, sp) * solve(q + 1, r, y);
}
}
}
}
return rt;
} int main() {
scanf("%d", &n);
n <<= 1;
for (int i = 1; i <= n; i++) {
scanf("%s", G[i] + 1);
}
memset(dp, -1, sizeof(dp));
ll ans = 0;
for (int i = 2; i <= n; i++) {
if (G[1][i] == '1') {
ans += solve(2, n, i);
}
}
printf("%lld\n", ans);
return 0;
}
Problem F Min Product Sum
Solution 1
可以认为同时填了两个矩形 $A, B$。
问题可以等价于 $B$ 中的元素 $B_{x, y}$ 满足小于等于 $A$ 中第 $x$ 行和第 $y$ 行的最小值的方案数。
设 $f_{k, i, j}$ 表示已经确定 $B$ 中 $i$ 行的最大值,以及 $A$ 中 $j$ 列的最小值,并且它们都小于等于 $k$。
转移考虑枚举 $B$ 中有多少行的最大值为 $(k + 1)$,然后已经确定最小值的列上在 $A$ 中填上大于等于 $(k + 1)$ 的数,没有确定最小值的列上在 $B$ 中填小于等于 $k + 1$ 的数,并满足在这些行中,每行至少有 1 个 $(k + 1)$。
然后枚举 $A$ 中有多少列的最小值为 $(k + 1)$,然后已经确定最大值的行上在 $A$ 中大于等于 $(k + 1)$ 的数,并保证这些列每列至少有一数为 $(k + 1)$,没有确定最大值的行上在 $B$ 中填上小于等于 $(k + 1)$。
时间复杂度 $O(KNM(N + M))$。
Code
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;
} else {
exgcd(b, a % b, y, x);
y -= (a / b) * x;
}
} int inv(int a, int n) {
int x, y;
exgcd(a, n, x, y);
return (x < 0) ? (x + n) : (x);
} int Mod; typedef class Z {
public:
int v; Z() : v(0) { }
Z(int x) : v(x){ }
Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) {
int x;
return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
}
friend Z operator - (const Z& a, const Z& b) {
int x;
return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
}
friend Z operator * (const Z& a, const Z& b) {
return Z(a.v * 1ll * b.v);
}
friend Z operator ~(const Z& a) {
return inv(a.v, Mod);
}
friend Z operator - (const Z& a) {
return Z(0) - a;
}
Z& operator += (Z b) {
return *this = *this + b;
}
Z& operator -= (Z b) {
return *this = *this - b;
}
Z& operator *= (Z b) {
return *this = *this * b;
}
friend boolean operator == (const Z& a, const Z& b) {
return a.v == b.v;
}
} Z; Z qpow(Z a, int p) {
Z rt = Z(1), pa = a;
for ( ; p; p >>= 1, pa = pa * pa) {
if (p & 1) {
rt = rt * pa;
}
}
return rt;
} typedef Z Zi; const int N = 102; int n, m, K;
Zi comb[N][N];
Zi f[2][N][N];
Zi coef1[N * N], ceof2[N]; int main() {
scanf("%d%d%d%d", &n, &m, &K, &Mod);
comb[0][0] = 1;
int qaq = max(n, m);
for (int i = 1; i <= qaq; i++) {
comb[i][0] = comb[i][i] = 1;
for (int j = 1; j < i; j++) {
comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j];
}
}
int cur = 0;
f[cur][0][0] = 1;
for (int k = 1; k <= K; k++) {
memset(f[cur ^= 1], 0, sizeof(f[0]));
coef1[0] = 1, coef1[1] = K - k + 1;
for (int i = 2, _ = n * m; i <= _; i++)
coef1[i] = coef1[i - 1] * coef1[1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
Zi v = f[cur ^ 1][i][j];
if (!v.v)
continue;
Zi y = qpow(k, m - j) - qpow(k - 1, m - j), pw = 1;
for (int x = 0; i + x <= n; x++, pw *= y)
f[cur][i + x][j] += v * comb[n - i][x] * coef1[x * j] * pw;
}
}
memset(f[cur ^= 1], 0, sizeof(f[0]));
coef1[0] = 1, coef1[1] = k;
for (int i = 2, _ = n * m; i <= _; i++)
coef1[i] = coef1[i - 1] * coef1[1];
for (int i = 0; i <= n; i++) {
Zi y = qpow(K - k + 1, i) - qpow(K - k, i);
for (int j = 0; j <= m; j++) {
Zi v = f[cur ^ 1][i][j];
if (!v.v)
continue;
Zi pw = 1;
for (int x = 0; j + x <= m; x++, pw *= y)
f[cur][i][j + x] += v * comb[m - j][x] * pw * coef1[(n - i) * x];
}
}
}
Zi ans = f[cur][n][m];
printf("%d\n", ans.v);
return 0;
}
Solution 2
题解做法太神仙了,听神仙 jerome_wei 说直接 dp 做法可以用容斥优化成 4 方,然后就又编了编。
考虑直接 dp,设 $f_{k, i, j}$ 表示已经确定 $i$ 行和 $j$ 列的最小值,每次暴力枚举最小值等于 $(k + 1)$ 的行列数量 $x, y$,然后在属于这 $(i + x)$ 并且属于 $(j + y)$ 列的空位置填上大于等于 $(k + 1)$ 的数,并且要求这 $x$ 行和 $y$ 列每行,每列至少有一个数为 $(k + 1)$。
考虑让行列的转移独立。问题在于枚举行后,有一些位置还不能填。考虑对行的限制进行容斥,用最小值恰好为 $(k + 1)$ 的方案数减去最小值恰好为 $(k + 2)$ 的方案数。对于列转移,显然能很好地限制每一列的最小值恰好为 $(k + 1)$。
暴力做法是,行转移的时候,枚举有 $x$ 行的最小值至少为 $(k + 1)$,再枚举 $y$ 行,它们是被硬点的不合法的部分。不难发现转移系数与已经确定的行数无关。因此可以预处理出转移系数。
总时间复杂度还是一样的。
Code
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;
} else {
exgcd(b, a % b, y, x);
y -= (a / b) * x;
}
} int inv(int a, int n) {
int x, y;
exgcd(a, n, x, y);
return (x < 0) ? (x + n) : (x);
} int Mod; typedef class Z {
public:
int v; Z() : v(0) { }
Z(int x) : v(x){ }
Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) {
int x;
return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
}
friend Z operator - (const Z& a, const Z& b) {
int x;
return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
}
friend Z operator * (const Z& a, const Z& b) {
return Z(a.v * 1ll * b.v);
}
friend Z operator ~(const Z& a) {
return inv(a.v, Mod);
}
friend Z operator - (const Z& a) {
return Z(0) - a;
}
Z& operator += (Z b) {
return *this = *this + b;
}
Z& operator -= (Z b) {
return *this = *this - b;
}
Z& operator *= (Z b) {
return *this = *this * b;
}
friend boolean operator == (const Z& a, const Z& b) {
return a.v == b.v;
}
} Z; Z qpow(Z a, int p) {
Z rt = Z(1), pa = a;
for ( ; p; p >>= 1, pa = pa * pa) {
if (p & 1) {
rt = rt * pa;
}
}
return rt;
} typedef Z Zi; const int N = 102; int n, m, K;
Zi comb[N][N];
Zi f[2][N][N];
Zi coef1[N][N]; int main() {
scanf("%d%d%d%d", &n, &m, &K, &Mod);
comb[0][0] = 1;
int qaq = max(n, m);
for (int i = 1; i <= qaq; i++) {
comb[i][0] = comb[i][i] = 1;
for (int j = 1; j < i; j++) {
comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j];
}
}
int cur = 0;
f[cur][0][0] = 1;
for (int k = 1; k <= K; k++) {
memset(f[cur ^= 1], 0, sizeof(f[0]));
for (int j = 0; j <= m; j++) {
for (int x = 0; x <= n; x++) {
coef1[j][x] = 0;
Zi pw = qpow(k, x * (m - j)), a = ~qpow(k, m - j) * qpow(k - 1, m - j);
for (int y = 0; y <= x; y++, pw *= a) {
Zi tmp = pw * comb[x][y];
if (y & 1) {
coef1[j][x] -= tmp;
} else {
coef1[j][x] += tmp;
}
}
coef1[j][x] *= qpow(K - k + 1, x * j);
}
}
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
Zi v = f[cur ^ 1][i][j];
if (!v.v)
continue;
for (int x = 0; i + x <= n; x++) {
f[cur][i + x][j] += coef1[j][x] * comb[n - i][x] * v;
}
}
}
memset(f[cur ^= 1], 0, sizeof(f[0]));
for (int i = 0; i <= n; i++) {
Zi a = qpow(K - k + 1, i) - qpow(K - k, i);
for (int j = 0; j <= m; j++) {
Zi v = f[cur ^ 1][i][j];
if (!v.v)
continue;
Zi pw = v, b = a * qpow(k, n - i);
for (int x = 0; j + x <= m; x++, pw *= b)
f[cur][i][j + x] += pw * comb[m - j][x];
}
}
}
Zi ans = f[cur][n][m];
printf("%d\n", ans.v);
return 0;
}