【AtCoder】AtCoder Grand Contest 023

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Zero-Sum Ranges

即计算前缀和相同的点对数,可以用 std::map 简单实现。

时间复杂度 O ( N L o g N ) O(NLogN)

#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

不难发现, ( A , B ) (A,B) 合法等价于 ( A + k , B + k ) (A+k,B+k) 合法。

因此我们只需要计算 A = 0 A=0 时的答案,然后将其乘以 N N 即可。

时间复杂度 O ( N 3 ) O(N^3)

#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

考虑计算 f ( i ) f(i) 表示操作前 i i 项后尚未全黑的排列数,则答案为
i = 0 N 1 f ( i ) \sum_{i=0}^{N-1}f(i)

为了计算 f ( i ) f(i) ,可以考虑计算 g ( i ) g(i) 表示大小为 i i 的操作后不会全黑的机器集合数,则
f ( i ) = g ( i ) × i ! × ( N 1 i ) ! f(i)=g(i)\times i!\times (N-1-i)!

可以采用容斥原理计算 g ( i ) g(i) ,即用总共的大小为 i i 的机器集合数减去可以全部涂黑的集合数。一个集合要能够全部涂黑,需要满足包含第一个机器、最后一个机器,并且每两个相邻的机器都要包含一个。

在每一个没有选择的机器前绑定一个被选择的机器,用隔板法计算集合数即可。

时间复杂度 O ( N ) O(N)

#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

首先,最后到站的一定是住在 1 1 号位置的人或住在 N N 号位置的人。

P 1 P N P_1\geq P_N ,则最后到站的是住在 N N 号位置的人;否则,最后到站的是住在 1 1 号位置的人。

考虑最后到站的是住在 N N 号位置的人的情况,住在 N N 号位置的人会希望 1 1 号位置尽早被经过,因为此后大巴的路线就是固定朝向 N N 号位置的了,因此,我们可以认为 P 1 P_1 增加了 P N P_N ,并删去 N N 号位置,将问题转化为一个规模更小的问题。

对最后到站的是住在 1 1 号位置的人的情况作同样的考虑即可。

时间复杂度 O ( N ) O(N)

#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

首先,满足条件的排列数 s s 是容易计算的,记 g e q i geq_i 表示 i \geq i 的数出现了多少次, c n t i = g e q i ( N i ) cnt_i=geq_i-(N-i) ,那么
s = i = 1 N c n t i s=\prod_{i=1}^{N}cnt_i

s = 0 s=0 ,则问题显然无解。

考虑枚举一对位置 i , j i,j ,计算使得 P i &gt; P j P_i&gt;P_j 的合法排列数,求和得到答案。

首先考虑 A i A j A_i\leq A_j 的情况,可以发现,若将 A j A_j 设为 A i A_i ,然后计算满足条件的排列数 s s&#x27; ,那么使得 P i &gt; P j P_i&gt;P_j 的合法排列数应当为 s 2 \frac{s&#x27;}{2}

考虑将 A j A_j 设为 A i A_i 将如何改变 c n t i cnt_i 数组,很明显,应当是使得 ( A i , A j ] (A_i,A_j] 内的所有元素减了 1 1 ,若记 v a l i = c n t i 1 c n t i val_i=\frac{cnt_i-1}{cnt_i} ,则
s = s k = A i + 1 A j v a l i s&#x27;=s\prod_{k=A_{i+1}}^{A_j}val_i

并且可以用前缀积加速 \prod 符号内的计算,即
s = s p r e j p r e i s&#x27;=s\frac{pre_j}{pre_i}

为了避免 O ( N 2 ) O(N^2) 的枚举,我们可以像一般的求逆序数的算法一样,从大到小枚举 i i ,并用树状数组统计每一个 j j 贡献的 p r e j pre_j 之和。

v a l i val_i 存在 0 0 时,前缀积的做法存在一些问题,但 ( i , j ] (i,j] 之间存在 v a l val 0 0 的位置同样意味着 s = 0 s&#x27;=0 ,因此可以以 v a l i val_i 0 0 的点为端点,将序列分为若干段,每一段分别计算前缀积。

对于 A i &gt; A j A_i&gt;A_j 的情况,不妨将 A i A_i 设置为 A j A_j ,然后计算满足条件的排列数 s s&#x27; ,那么使得 P i &gt; P j P_i&gt;P_j 的合法排列数应当为 s s 2 s-\frac{s&#x27;}{2}

时间复杂度 O ( N L o g N ) O(NLogN)

#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

第一个选择的节点一定是根节点。

考虑接下来的问题,我们有若干棵子树,需要选择一棵子树的根节点作为第二个选择的节点。如果有一颗子树的根节点权值为 0 0 ,那么先选择它一定是不劣的。

更进一步地考虑,任何一个权值为 0 0 的节点的父亲一旦被选择,在接下来选择该节点一定是不劣的。

这启发我们可以将权值为 0 0 的节点与其父亲看做一个整体考虑,影响每一个整体排序的因素应当为其中 0 0 的个数 c n t 0 cnt_0 ,和 1 1 的个数 c n t 1 cnt_1 。如果能够无视拓扑序的限制,我们显然会优先将 c n t 0 c n t 0 + c n t 1 \frac{cnt_0}{cnt_0+cnt_1} 较大的整体排在前面,这一点的最优性可以通过调整法来证明。

那么, c n t 0 c n t 0 + c n t 1 \frac{cnt_0}{cnt_0+cnt_1} 最大的整体应当相当于一个权值为 0 0 的节点,其父亲一旦被选择,在接下来选择该节点一定是不劣的。

用堆模拟这个树上合并的过程即可。

时间复杂度 O ( N L o g N ) O(NLogN)

#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;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/100746440