比赛链接
官方题解
Problem A. Zero-Sum Ranges
即计算前缀和相同的点对数,可以用 std::map 简单实现。
时间复杂度 。
#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("");
}
map <ll, int> mp;
int main() {
int n; read(n);
ll s = 0, ans = 0; mp[s]++;
for (int i = 1; i <= n; i++) {
int x; read(x); s += x;
ans += mp[s], mp[s]++;
}
writeln(ans);
return 0;
}
Problem B. Find Symmetries
不难发现, 合法等价于 合法。
因此我们只需要计算 时的答案,然后将其乘以 即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
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 main() {
int n; read(n);
for (int i = 1; i <= n; i++)
scanf("\n%s", s[i] + 1);
int ans = 0;
for (int p = 1; p <= n; p++) {
bool flg = true;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
flg &= s[i][j] == s[j][i];
if (flg) ans += n;
for (int i = 1; i <= n; i++) {
char c = s[i][1];
for (int j = 1; j <= n - 1; j++)
s[i][j] = s[i][j + 1];
s[i][n] = c;
}
}
writeln(ans);
return 0;
}
Problem C. Painting Machines
考虑计算
表示操作前
项后尚未全黑的排列数,则答案为
为了计算
,可以考虑计算
表示大小为
的操作后不会全黑的机器集合数,则
可以采用容斥原理计算 ,即用总共的大小为 的机器集合数减去可以全部涂黑的集合数。一个集合要能够全部涂黑,需要满足包含第一个机器、最后一个机器,并且每两个相邻的机器都要包含一个。
在每一个没有选择的机器前绑定一个被选择的机器,用隔板法计算集合数即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
const int P = 1e9 + 7;
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 fac[MAXN], inv[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;
}
int binom(int x, int y) {
if (y > x) return 0;
else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = 1ll * fac[i - 1] * i % P;
inv[n] = power(fac[n], P - 2);
for (int i = n - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int sbinom(int cnt, int sum) {
assert(cnt != 0);
if (sum < 0) return 0;
else return binom(sum + cnt - 1, cnt - 1);
}
int main() {
int n, ans = 0;
read(n), init(n);
update(ans, fac[n - 1]);
for (int i = 1; i <= n - 1; i++) {
int cnt = binom(n - 1, i), lft = n - 1 - i;
update(cnt, P - sbinom(lft + 1, n - 1 - 2 * lft));
if (lft) update(cnt, sbinom(lft, n - 1 - 2 * lft));
update(ans, 1ll * cnt * fac[i] % P * fac[n - 1 - i] % P);
}
writeln(ans);
return 0;
}
Problem D. Go Home
首先,最后到站的一定是住在 号位置的人或住在 号位置的人。
若 ,则最后到站的是住在 号位置的人;否则,最后到站的是住在 号位置的人。
考虑最后到站的是住在 号位置的人的情况,住在 号位置的人会希望 号位置尽早被经过,因为此后大巴的路线就是固定朝向 号位置的了,因此,我们可以认为 增加了 ,并删去 号位置,将问题转化为一个规模更小的问题。
对最后到站的是住在 号位置的人的情况作同样的考虑即可。
时间复杂度 。
#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 n, s, x[MAXN];
ll ans, p[MAXN];
int main() {
read(n), read(s);
for (int i = 1; i <= n; i++)
read(x[i]), read(p[i]);
int l = 1, r = n, last = -1;
while (l <= r) {
if (x[r] < s || (x[l] < s && p[l] < p[r])) {
if (last != -1) ans += abs(x[l] - last);
last = x[l], p[r] += p[l++];
} else {
if (last != -1) ans += abs(x[r] - last);
last = x[r], p[l] += p[r--];
}
}
ans += abs(s - last);
writeln(ans);
return 0;
}
Problem E. Inversions
首先,满足条件的排列数
是容易计算的,记
表示
的数出现了多少次,
,那么
若 ,则问题显然无解。
考虑枚举一对位置 ,计算使得 的合法排列数,求和得到答案。
首先考虑 的情况,可以发现,若将 设为 ,然后计算满足条件的排列数 ,那么使得 的合法排列数应当为 。
考虑将
设为
将如何改变
数组,很明显,应当是使得
内的所有元素减了
,若记
,则
并且可以用前缀积加速
符号内的计算,即
为了避免 的枚举,我们可以像一般的求逆序数的算法一样,从大到小枚举 ,并用树状数组统计每一个 贡献的 之和。
当 存在 时,前缀积的做法存在一些问题,但 之间存在 为 的位置同样意味着 ,因此可以以 为 的点为端点,将序列分为若干段,每一段分别计算前缀积。
对于 的情况,不妨将 设置为 ,然后计算满足条件的排列数 ,那么使得 的合法排列数应当为 。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7;
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("");
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
struct BinaryIndexTree {
int n, a[MAXN];
void init(int x) {
n = x;
memset(a, 0, sizeof(a));
}
void modify(int x, int d) {
for (int i = x; i <= n; i += i & -i)
update(a[i], d);
}
int query(int l, int r) {
int ans = 0;
for (int i = r; i >= 1; i -= i & -i)
update(ans, a[i]);
for (int i = l - 1; i >= 1; i -= i & -i)
update(ans, P - a[i]);
return ans;
}
} BIT, CIT;
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 n, s, a[MAXN], cnt[MAXN];
int Max[MAXN], pre[MAXN], val[MAXN];
int main() {
read(n), s = 1;
for (int i = 1; i <= n; i++)
read(a[i]), cnt[a[i]]++;
for (int i = n; i >= 1; i--)
cnt[i] += cnt[i + 1];
for (int i = 1; i <= n; i++) {
cnt[i] -= n - i;
s = 1ll * s * cnt[i] % P;
}
if (s == 0) {
puts("0");
return 0;
}
for (int i = 1; i <= n; i++)
val[i] = (cnt[i] - 1ll) * power(cnt[i], P - 2) % P;
int last = 0; pre[0] = 1;
for (int i = 1; i <= n; i++) {
if (val[i] == 0) {
for (int j = last; j <= i - 1; j++)
Max[j] = i - 1;
last = i, pre[i] = 1;
} else pre[i] = 1ll * pre[i - 1] * val[i] % P;
}
for (int i = last; i <= n; i++)
Max[i] = n;
int ans = 0; BIT.init(n);
for (int i = n; i >= 1; i--) {
update(ans, 1ll * s * power(2ll * pre[a[i]] % P, P - 2) % P * BIT.query(a[i], Max[a[i]]) % P);
BIT.modify(a[i], pre[a[i]]);
}
BIT.init(n), CIT.init(n);
for (int i = 1; i <= n; i++) {
update(ans, P - 1ll * s * power(2ll * pre[a[i]] % P, P - 2) % P * BIT.query(a[i] + 1, Max[a[i]]) % P);
update(ans, 1ll * s * CIT.query(a[i] + 1, n) % P);
BIT.modify(a[i], pre[a[i]]);
CIT.modify(a[i], 1);
}
writeln(ans);
return 0;
}
Problem F. 01 on Tree
第一个选择的节点一定是根节点。
考虑接下来的问题,我们有若干棵子树,需要选择一棵子树的根节点作为第二个选择的节点。如果有一颗子树的根节点权值为 ,那么先选择它一定是不劣的。
更进一步地考虑,任何一个权值为 的节点的父亲一旦被选择,在接下来选择该节点一定是不劣的。
这启发我们可以将权值为 的节点与其父亲看做一个整体考虑,影响每一个整体排序的因素应当为其中 的个数 ,和 的个数 。如果能够无视拓扑序的限制,我们显然会优先将 较大的整体排在前面,这一点的最优性可以通过调整法来证明。
那么, 最大的整体应当相当于一个权值为 的节点,其父亲一旦被选择,在接下来选择该节点一定是不劣的。
用堆模拟这个树上合并的过程即可。
时间复杂度 。
#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("");
}
struct info {int cnt[2]; ll inv; } ans[MAXN];
info operator + (info a, info b) {
info ans;
ans.cnt[0] = a.cnt[0] + b.cnt[0];
ans.cnt[1] = a.cnt[1] + b.cnt[1];
ans.inv = a.inv + b.inv + 1ll * a.cnt[1] * b.cnt[0];
return ans;
}
bool operator == (info a, info b) {
return a.cnt[0] == b.cnt[0] && a.cnt[1] == b.cnt[1] && a.inv == b.inv;
}
bool operator < (info a, info b) {
return 1ll * a.cnt[1] * b.cnt[0] < 1ll * a.cnt[0] * b.cnt[1];
}
struct hinfo {int pos; info val; };
bool operator < (hinfo a, hinfo b) {
if (a.val < b.val) return false;
else if (b.val < a.val) return true;
else return a.pos < b.pos;
}
int n, f[MAXN], p[MAXN], v[MAXN];
priority_queue <hinfo> Heap;
int F(int x) {
if (f[x] == x) return x;
else return f[x] = F(f[x]);
}
int main() {
read(n);
for (int i = 2; i <= n; i++)
read(p[i]);
for (int i = 1; i <= n; i++) {
read(v[i]), f[i] = i;
ans[i].cnt[v[i]]++;
}
for (int i = 2; i <= n; i++)
Heap.push((hinfo) {i, ans[i]});
while (!Heap.empty()) {
hinfo tmp = Heap.top(); Heap.pop();
if (tmp.val == ans[tmp.pos]) {
int x = tmp.pos, y = F(p[x]); f[x] = y;
ans[y] = ans[y] + ans[x];
if (y != 1) Heap.push((hinfo) {y, ans[y]});
}
}
writeln(ans[1].inv);
return 0;
}