比赛链接
官方题解
Problem A. 01 Matrix
将子矩阵 和 标为 ,剩余部分标为 即可。
时间复杂度 。
#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 main() {
int n, m, a, b;
read(n), read(m), read(a), read(b);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
if (i <= b && j <= a) putchar('1');
else if (i > b && j > a) putchar('1');
else putchar('0');
puts("");
}
return 0;
}
Problem B. Sorting a Segment
考虑排序后的结果,若所排序的区间原本就是有序的,那么得到的序列就是原序列,所有排序有序区间的方案对应的结果是相同的。
否则,一定改变了某些元素的位置,不难发现若排序 的结果和排序 的结果一样,那么排序 的结果也一样,因此我们只需要统计排序 的结果不同的 的个数即可。
显然,排序 的结果不同当且仅当 是 中的最小值, 是 中的最大值,用 表或是单调队列解决即可。
时间复杂度 或 。
#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("");
}
namespace rmq {
const int MAXN = 2e5 + 5;
const int MAXLOG = 20;
int Max[MAXN][MAXLOG], Min[MAXN][MAXLOG], Log[MAXN];
int queryMax(int l, int r) {
int len = r - l + 1, tmp = Log[len];
return max(Max[l][tmp], Max[r - (1 << tmp) + 1][tmp]);
}
int queryMin(int l, int r) {
int len = r - l + 1, tmp = Log[len];
return min(Min[l][tmp], Min[r - (1 << tmp) + 1][tmp]);
}
void init(int *a, int n) {
for (int i = 1; i <= n; i++) {
Min[i][0] = a[i];
Max[i][0] = a[i];
Log[i] = Log[i - 1];
if ((1 << (Log[i] + 1)) <= i) Log[i]++;
}
for (int t = 1; t < MAXLOG; t++)
for (int i = 1, j = (1 << (t - 1)) + 1; j <= n; i++, j++) {
Max[i][t] = max(Max[i][t - 1], Max[j][t - 1]);
Min[i][t] = min(Min[i][t - 1], Min[j][t - 1]);
}
}
}
int n, k, a[MAXN];
bool flg[MAXN];
int main() {
read(n), read(k);
for (int i = 1; i <= n; i++)
read(a[i]);
rmq :: init(a, n);
int ans = 0, now = 0;
for (int i = 1; i <= k - 1; i++)
now += a[i] < a[i + 1];
for (int i = 1, j = k; j <= n; i++, j++) {
if (now == k - 1) {
ans = 1;
flg[i] = true;
}
now -= a[i] < a[i + 1];
now += a[j] < a[j + 1];
}
for (int i = 1, j = k; j <= n; i++, j++) {
if (flg[i]) continue;
if (i != 1 && a[i - 1] < rmq :: queryMin(i, j - 1) && a[j] > rmq :: queryMax(i, j - 1)) continue;
else ans++;
}
writeln(ans);
return 0;
}
Problem C. LCMs
考虑计算 ,最后减去 ,再除以 得到答案。
令
,有
在上式中枚举
即可,需要计算
和
卷积后的结果,以及输入中是
的倍数的数之和
,然后有
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 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("");
}
int tot, prime[MAXN], f[MAXN];
int miu[MAXN], inv[MAXN], func[MAXN];
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;
}
void sieve(int n) {
miu[1] = 1;
for (int i = 2; i <= n; i++) {
if (f[i] == 0) {
prime[++tot] = f[i] = i;
miu[i] = P - 1;
}
for (int j = 1; j <= tot && prime[j] <= f[i]; j++) {
int tmp = prime[j] * i;
if (tmp > n) break;
f[tmp] = prime[j];
if (prime[j] == f[i]) miu[tmp] = 0;
else miu[tmp] = (P - miu[i]) % P;
}
}
for (int i = 1; i <= n; i++) {
inv[i] = power(i, P - 2);
for (int j = 1; i * j <= n; j++)
update(func[i * j], 1ll * inv[i] * miu[j] % P);
}
}
int a[MAXN], cnt[MAXN], value[MAXN];
int main() {
int n; read(n);
int m = 1e6; sieve(m);
for (int i = 1; i <= n; i++)
read(a[i]), cnt[a[i]]++;
for (int i = 1; i <= m; i++) {
for (int j = i; j <= m; j += i)
update(value[i], 1ll * j * cnt[j] % P);
}
int ans = 0;
for (int i = 1; i <= m; i++)
update(ans, 1ll * value[i] * value[i] % P * func[i] % P);
for (int i = 1; i <= n; i++)
update(ans, P - a[i]);
writeln(1ll * ans * inv[2] % P);
return 0;
}
Problem D. Unique Path
首先特判 的情况,此时图是一棵树,不允许出现 的限制。
考虑 的限制,若将这些限制的 看做边,将会得到若干个连通块,不难证明,在原图中,这些连通块的导出子图均为树,不妨先将这些树连出来。
令剩余边数为 ,连通块数为 ,由于 , 。
对于 的限制,若连接了两个同一棵树中的节点,则答案为 。
否则,我们可以从每一棵树中取出一个点来连成一个环,以满足 的限制,但由于至多连成一个完全图,因此还需要满足 。
的情况需要特判,此时不允许出现 的限制。
时间复杂度 。
#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 f[MAXN];
int F(int x) {
if (f[x] == x) return x;
else return f[x] = F(f[x]);
}
int x[MAXN], y[MAXN], type[MAXN];
int main() {
int n, q; ll m;
read(n), read(m), read(q);
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 1; i <= q; i++) {
read(x[i]), read(y[i]), read(type[i]);
x[i]++, y[i]++;
if (type[i] == 0) {
f[F(x[i])] = F(y[i]);
} else {
if (m == n - 1) {
puts("No");
return 0;
}
}
}
for (int i = 1; i <= q; i++)
if (type[i] && F(x[i]) == F(y[i])) {
puts("No");
return 0;
}
int cnt = 0;
for (int i = 1; i <= n; i++)
cnt += F(i) == i;
if (cnt <= 2) {
for (int i = 1; i <= q; i++)
if (type[i]) {
puts("No");
return 0;
}
}
ll ans = n - 1 - (cnt - 1);
ans += 1ll * cnt * (cnt - 1) / 2;
if (ans >= m) puts("Yes");
else puts("No");
return 0;
}
Problem E. Gachapon
考虑计算到达各个尚未完成的状态的概率之和,由期望的线性性,这也是答案。
令所有状态的指数型生成函数为 ,普通型生成函数为 ,已经完成的状态的指数型生成函数为 ,普通型生成函数为 ,那么我们要求的就是 。
考虑计算
,令
,有
令
,那么
暴力展开 的计算式,对于各个 ,展开关于 的多项式的时间复杂度均为 ,因此展开的时间复杂度为 。
接下来考虑如何将 转为普通型生成函数求出 。
对于形如
的项,其对答案的贡献显然为
考虑形如
的项,其对答案的贡献应当为
考虑对于
,计算
等式两侧同时乘以
,则有
那么
将 用二项式定理展开,即可将问题递归至更小的规模。
本部分时间复杂度也为 。
总时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 405;
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("");
}
int n, sa, sb, a[MAXN], b[MAXN];
int f[MAXN][MAXN], g[MAXN], coef[MAXN][MAXN];
int fac[MAXN], inv[MAXN], binom[MAXN][MAXN];
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;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
read(n), sa = 0, f[0][0] = 1;
for (int i = 1; i <= n; i++) {
read(a[i]), read(b[i]);
sa += a[i], sb += b[i];
}
fac[0] = inv[0] = binom[0][0] = 1;
for (int i = 1; i <= max(sa, sb); i++) {
fac[i] = 1ll * fac[i - 1] * i % P;
inv[i] = power(fac[i], P - 2);
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 x = 1, s = 0; x <= n; s += b[x++]) {
memset(g, 0, sizeof(g));
int p = 1ll * a[x] * power(sa, P - 2) % P;
for (int i = 0, now = 1; i <= b[x] - 1; i++, now = 1ll * now * p % P)
g[i] = (P - 1ll * now * inv[i] % P) % P;
for (int i = sa; i >= 0; i--) {
static int tmp[MAXN];
memset(tmp, 0, sizeof(tmp));
for (int j = 0; j <= b[x]; j++)
for (int k = 0; k <= s; k++) {
update(tmp[j + k], 1ll * f[i][k] * g[j] % P);
}
memcpy(f[i], tmp, sizeof(tmp));
if (i >= a[x]) {
for (int j = 0; j <= s; j++)
update(f[i][j], f[i - a[x]][j]);
}
}
}
assert(f[sa][0] == 1);
int ans = 0;
for (int i = 0; i <= sb; i++)
update(ans, 1ll * f[0][i] * fac[i] % P);
coef[0][0] = 1;
for (int i = 1; i <= sb; i++)
for (int j = i; j >= 0; j--) {
coef[i][j] = 1ll * coef[i - 1][j] * i % P;
if (j != 0) update(coef[i][j], coef[i - 1][j - 1]);
}
for (int i = 1; i <= sa - 1; i++) {
static int res[MAXN];
int p = 1ll * i * power(sa, P - 2) % P;
memset(res, 0, sizeof(res)), res[0] = power((1 - p + P) % P, P - 2);
for (int j = 1; j <= sb; j++) {
int tmp = 0;
for (int k = 0; k <= j - 1; k++)
update(tmp, 1ll * binom[j][k] * res[k] % P);
res[j] = 1ll * tmp * p % P * res[0] % P;
}
for (int j = 0; j <= sb; j++) {
int tmp = 0;
for (int k = 0; k <= j; k++)
update(tmp, 1ll * res[k] * coef[j][k] % P);
update(ans, 1ll * tmp * f[i][j] % P);
}
}
writeln((P - ans) % P);
return 0;
}
Problem F. Two Permutations
将输入的排列 看做两个置换,则其各个环或是整体被改为连向自身,或是不作修改。
注意到时间限制对于 的数据规模来说异常地大,考虑用最小割解题。
对于一个位置
,有如下几种情况:
、
:此时无论是否改变都不会对答案造成影响。
、
:此时只改变
中的一个可以使答案
。
、
:此时改变
会使得答案
。
、
:此时改变
会使得答案
。
、
互不相同:同时改变
会使得答案
。
由于要使用最小割解题,我们不希望出现增益的条件,考虑对
稍作修改。
、
:此时不改变
,或同时改变
会使得答案
。
对于 中的点,令将其划分在 集表示改变,划分在 集表示不改变;对于 中的点,令将其划分在 集表示改变,划分在 集表示不改变。
那么对于上面的情况,我们分别可以按照如下方式连边。
、
、
、
、
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int INF = 2e9;
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("");
}
namespace NetworkFlow {
const int INF = 2e9;
const int MAXP = 5e5 + 5;
struct edge {
int dest, flow;
unsigned pos;
};
vector <edge> a[MAXP];
int tot, s, t, dist[MAXP];
unsigned curr[MAXP];
void addedge(int x, int y, int z) {
a[x].push_back((edge) {y, z, a[y].size()});
a[y].push_back((edge) {x, 0, a[x].size() - 1});
}
int dinic(int pos, int limit) {
if (pos == t) return limit;
int used = 0, tmp;
for (unsigned &i = curr[pos]; i < a[pos].size(); i++)
if (a[pos][i].flow != 0 && dist[pos] + 1 == dist[a[pos][i].dest] && (tmp = dinic(a[pos][i].dest, min(limit - used, a[pos][i].flow)))) {
used += tmp;
a[pos][i].flow -= tmp;
a[a[pos][i].dest][a[pos][i].pos].flow += tmp;
if (used == limit) return used;
}
return used;
}
bool bfs() {
static int q[MAXP];
int l = 0, r = 0;
memset(dist, 0, sizeof(dist));
dist[s] = 1, q[0] = s;
while (l <= r) {
int tmp = q[l];
for (unsigned i = 0; i < a[tmp].size(); i++)
if (dist[a[tmp][i].dest] == 0 && a[tmp][i].flow != 0) {
q[++r] = a[tmp][i].dest;
dist[q[r]] = dist[tmp] + 1;
}
l++;
}
return dist[t] != 0;
}
int flow() {
int ans = 0;
while (bfs()) {
memset(curr, 0, sizeof(curr));
ans += dinic(s, INF);
}
return ans;
}
}
int n, a[MAXN], b[MAXN];
int va[MAXN], vb[MAXN];
int main() {
int n; read(n);
for (int i = 1; i <= n; i++)
read(a[i]), a[i]++;
for (int i = 1; i <= n; i++)
read(b[i]), b[i]++;
for (int i = 1; i <= n; i++) {
if (va[i]) continue;
int pos = i;
while (va[pos] == 0) {
va[pos] = i;
pos = a[pos];
}
}
for (int i = 1; i <= n; i++) {
if (vb[i]) continue;
int pos = i;
while (vb[pos] == 0) {
vb[pos] = i + n;
pos = b[pos];
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
ans += a[i] != b[i];
int s = NetworkFlow :: s = 2 * n + 1;
int t = NetworkFlow :: t = 2 * n + 2;
for (int i = 1; i <= n; i++) {
if (a[i] == b[i]) {
if (a[i] == i) continue; ans += 1;
NetworkFlow :: addedge(vb[i], va[i], 1);
NetworkFlow :: addedge(va[i], vb[i], 1);
} else {
if (a[i] == i) NetworkFlow :: addedge(s, vb[i], 1);
else if (b[i] == i) NetworkFlow :: addedge(va[i], t, 1);
else NetworkFlow :: addedge(va[i], vb[i], 1);
}
}
ans -= NetworkFlow :: flow();
writeln(ans);
return 0;
}