【CodeForces】Codeforces Round 626

比赛链接

点击打开链接

官方题解

点击打开链接

Problem A. Unusual Competitions

显然,当且仅当左右括号的个数不相等,答案为 1 -1 。否则,将左右括号分别看做 + 1 , 1 +1,-1 ,画出前缀和的折线图,不难发现翻转 x x 轴下方的部分是最优的。

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

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
typedef long long ll;
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;
}
char s[MAXN];
int main() {
	int n; read(n);
	scanf("%s", s + 1);
	int cur = 0, ans = 0, last = 0;
	for (int i = 1; i <= n; i++) {
		if (s[i] == '(') {
			cur += 1;
			if (cur == 0) ans += i - last;
		} else {
			cur -= 1;
			if (cur == -1) last = i - 1;
		}
	}
	if (cur != 0) puts("-1");
	else cout << ans << endl;
	return 0;
}

Problem B. Present

考虑分别求出答案的每一位。

若我们希望求出答案在 2 i 2^i 处的值,可以将所有数对 2 i + 1 2^{i+1} 取模,并排序。
此时,两个数 x , y x,y 的和对答案产生贡献,当且仅当 x + y [ 2 i , 2 i + 1 ) [ 2 i + 1 + 2 i , 2 i + 2 ) x+y\in[2^i,2^{i+1})\cup[2^{i+1}+2^i,2^{i+2})

用前缀和或双指针均可解决。

时间复杂度 O ( V + N L o g V ) O(V+NLogV)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e5 + 5;
const int MAXV = 1 << 25;
typedef long long ll;
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;
}
int n, a[MAXN];
bool getans(int p) {
	static int sum[MAXV];
	int bit = (1 << (p + 1)) - 1, goal = 1 << p;
	for (int i = 0; i <= bit; i++)
		sum[i] = 0;
	for (int i = 1; i <= n; i++)
		sum[a[i] & bit]++;
	for (int i = 1; i <= bit; i++)
		sum[i] += sum[i - 1];
	bool ans = false; int last = 0;
	for (int i = 0; i <= bit; i++) {
		int cnt = sum[i] - last;
		if (cnt != 0) {
			if (((i + i) & bit) >= goal) ans ^= 1ll * cnt * (cnt - 1) / 2 % 2 == 1;
			int l = (goal - i + bit + 1) & bit, r = bit - i, tmp = 0;
			if (cnt & 1) {
				if (l <= r) {
					if (r > i) tmp = sum[r] - sum[max(l - 1, i)];
				} else {
					tmp = sum[bit] - sum[max(l - 1, i)]; 
					if (r > i) tmp += sum[r] - sum[i];
				}
			}
			if (tmp & 1) ans ^= true;
		}
		last = sum[i];
	}
	return ans;
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	int ans = 0;
	for (int p = 0; p <= 24; p++) {
		bool flg = getans(p);
		if (flg) ans ^= 1 << p;
	}
	cout << ans << endl;
	return 0;
}

Problem C. Instant Noodles

删去右侧的孤点,它们显然对答案没有影响。

为左侧的每一个点随机一个 64 64 位无符号整型的权值 v i v_i
令右侧的每一个点的权值 b i b_i 为左侧所有与其相邻的点 v i v_i 的异或和。

那么,可以认为,当且仅当右侧两个点的 b i b_i 相同,它们始终会在 N ( S ) N(S) 中一起出现,或一起不出现。因此,这样的两个点可以被缩成一个点来看待。

可以证明,答案即为缩点后各个点 c i c_i 的 GCD 。

单组数据时间复杂度 O ( M + N L o g N ) O(M+NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
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;
}
ll a[MAXN]; ull b[MAXN], v[MAXN];
map <ull, ll> mp; int n, m;
ull rnd() {
	ull ans = 0;
	for (int i = 0; i <= 7; i++) {
		ans <<= 8;
		ans ^= rand() & 255;
	}
	return ans;
}
int main() {
	srand('X' + 'Y' + 'X');
	int T; read(T);
	while (T--) {
		read(n), read(m);
		for (int i = 1; i <= n; i++) {
			read(a[i]), b[i] = 0;
			v[i] = rnd();
		}
		for (int i = 1; i <= m; i++) {
			int x, y; read(x), read(y);
			b[y] ^= v[x];
		}
		mp.clear();
		for (int i = 1; i <= n; i++)
			if (b[i] != 0) mp[b[i]] += a[i];
		ll ans = 0;
		for (auto x : mp)
			ans = __gcd(ans, x.second);
		printf("%lld\n", ans);
	}
	return 0;
}

Problem D. Reality Show

考虑将序列倒置,我们希望找出一个不降的子序列,同时最大化其关于二进制下进位的权值。

考虑一个朴素的动态规划,记 d p i , j , k dp_{i,j,k} 表示考虑了序列的前 i i 位,当前的二进制数为 k × 2 j k\times 2^j
转移时考虑放弃当前的元素,选择当前的元素,或进位,不难得出 O ( 1 ) O(1) 的转移。

观察上述 DP ,可以发现,从 d p i , j , k dp_{i,j,k} d p i + 1 , j , k dp_{i+1,j,k} ,产生变动的位置只有 O ( N + M ) O(N+M) 个,因此可以去掉第一维,仅修改这些位置,滚动数组的同时优化时间复杂度。

时间复杂度 O ( N L o g N × ( N + M ) ) O(NLogN\times (N+M)) ,可优化至 O ( N ( N + M ) ) O(N(N+M))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4005;
const int INF  = 1e9 + 7;
typedef long long ll;
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;
}
int n, m, l[MAXN], s[MAXN], c[MAXN], dp[MAXN][MAXN];
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
		read(l[i]);
	for (int i = 1; i <= n; i++)
		read(s[i]);
	reverse(l + 1, l + n + 1);
	reverse(s + 1, s + n + 1);
	for (int i = 1; i <= n + m; i++)
		read(c[i]);
	for (int i = 1; i <= n + m; i++)
	for (int j = 1; j <= n; j++)
		dp[i][j] = -INF;
	for (int i = 1; i <= n; i++) {
		int cur = l[i];
		for (int j = n; j >= 0; j--) {
			if (dp[cur][j] == -INF) continue;
			int tmp = dp[cur][j] - s[i] + c[cur];
			for (int k = cur + 1, t = j; t & 1; k++, t >>= 1)
				tmp += c[k];
			for (int k = cur, t = j + 1; t != 0; k++, t >>= 1)
				chkmax(dp[k][t], tmp);
		}
		for (int j = 1; j <= n + m; j++) {
			chkmax(dp[j][0], dp[j - 1][0]);
			chkmax(dp[j][0], dp[j - 1][1]);
		}
	}
	int ans = -INF;
	for (int i = 1; i <= n + m; i++)
	for (int j = 0; j <= n; j++)
		chkmax(ans, dp[i][j]);
	cout << ans << endl;
	return 0;
}

Problem E. Median Mountain Range

考虑权值范围在 { 0 , 1 } \{0,1\} 内的问题。
结构 0 , 0 0,0 和结构 1 , 1 1,1 是稳定的,因此答案应当为最长的 0 , 1 0,1 交替的段除以 2 2

那么,原问题中,考虑选取中间值 M i d Mid ,令 M i d \geq Mid 的元素为 1 1 < M i d <Mid 的元素为 0 0 ,操作进行的轮数即为所有中间值中,使得范围在 { 0 , 1 } \{0,1\} 内的问题操作轮数最多的轮数。

由此,我们需要一个结构,支持将一个元素从 0 0 修改为 1 1 ,同时维护当前最长的 0 , 1 0,1 交替的段的长度,并且求出修改后,最终状态新增了那些 1 1
以下代码通过维护稳定位置的方式实现了这一算法。

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

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
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;
}
int ans[MAXN];
struct SegmentTree {
	struct Node {
		int lc, rc;
		int sum;
	} a[MAXN * 2];
	int n, root, size;
	void update(int root) {
		a[root].sum = a[a[root].lc].sum + a[a[root].rc].sum;
	}
	void build(int &root, int l, int r) {
		root = ++size;
		if (l == r) {
			a[root].sum = 1;
			return;
		}
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
		update(root);
	}
	void init(int x) {
		n = x;
		build(root, 1, n);
	}
	void modify(int root, int l, int r, int ql, int qr, int v) {
		if (l == ql && r == qr) {
			if (a[root].sum == 0) return;
			if (l == r) {
				a[root].sum = 0;
				ans[l] = v;
				return;
			}
			int mid = (l + r) / 2;
			modify(a[root].lc, l, mid, l, mid, v);
			modify(a[root].rc, mid + 1, r, mid + 1, r, v);
			update(root);
			return;
		}
		int mid = (l + r) / 2;
		if (mid >= ql) modify(a[root].lc, l, mid, ql, min(mid, qr), v);
		if (mid + 1 <= qr) modify(a[root].rc, mid + 1, r, max(mid + 1, ql), qr, v);
		update(root);
	}
	void modify(int l, int r, int v) {
		modify(root, 1, n, l, r, v);
	}
} ST;
set <int> Break;
int n, col[MAXN];
struct DataStructure {
	int cnt[MAXN];
	set <int> Max;
	void inc(int x) {
		cnt[x]++;
		if (cnt[x] == 1) Max.insert(x);
	}
	void dec(int x) {
		cnt[x]--;
		if (cnt[x] == 0) Max.erase(x);
	}
	int query() {
		auto tmp = Max.end(); tmp--;
		return (*tmp);
	}
} D;
void inc(int pos, int val) {
	if (pos == 0 || pos == n) {
		col[pos] += 2;
		if (pos == 0) {
			auto tmp = Break.lower_bound(pos);
			auto suf = tmp; suf++;
			if (col[*suf] == 2) ST.modify((*tmp) + 1, *suf, val);
			else ST.modify((*tmp) + 1, ((*tmp) + (*suf)) / 2, val);
		} else {
			auto tmp = Break.lower_bound(pos);
			auto pre = tmp; pre--;
			if (col[*pre] == 2) ST.modify((*pre) + 1, *tmp, val);
			else ST.modify(((*pre) + (*tmp)) / 2 + 1, *tmp, val);
		}
	} else if (col[pos] == 0) {
		col[pos] += 1;
		auto tmp = Break.lower_bound(pos);
		auto pre = tmp, suf = tmp; pre--, suf++;
		D.dec((*tmp) - (*pre)), D.dec((*suf) - (*tmp)), D.inc((*suf) - (*pre));
		if (col[*pre] == 2 && col[*suf] == 2) ST.modify((*pre) + 1, *suf, val);
		else if (col[*pre] == 2) ST.modify((*pre) + 1, ((*pre) + (*suf)) / 2, val);
		else if (col[*suf] == 2) ST.modify(((*pre) + (*suf)) / 2 + 1, *suf, val);
		Break.erase(tmp);
	} else {
		col[pos] += 1;
		auto tmp = Break.insert(pos).first;
		auto pre = tmp, suf = tmp; pre--, suf++;
		D.inc((*tmp) - (*pre)), D.inc((*suf) - (*tmp)), D.dec((*suf) - (*pre));
		if (col[*suf] == 2) ST.modify((*tmp) + 1, *suf, val);
		else ST.modify((*tmp) + 1, ((*tmp) + (*suf)) / 2, val);
		if (col[*pre] == 2) ST.modify((*pre) + 1, *tmp, val);
		else ST.modify(((*pre) + (*tmp)) / 2 + 1, *tmp, val);
	}
}
int main() {
	read(n);
	static int p[MAXN], a[MAXN];
	for (int i = 1; i <= n; i++) {
		read(a[i]);
		p[i] = i;
	}
	sort(p + 1, p + n + 1, [&] (int x, int y) {return a[x] > a[y]; });
	for (int i = 0; i <= n; i++)
		Break.insert(i);
	for (int i = 1; i <= n; i++) 
		D.inc(1);
	int Max = 1; ST.init(n);
	for (int i = 1; i <= n; i++) {
		inc(p[i], a[p[i]]), inc(p[i] - 1, a[p[i]]);
		if (a[p[i]] != a[p[i + 1]]) chkmax(Max, D.query());
	}
	printf("%d\n", (Max - 1) / 2);
	for (int i = 1; i <= n; i++)
		printf("%d ", ans[i]);
	printf("\n");
	return 0;
}

Problem F. Assigning Fares

考虑用其两侧点 c i c_i 的大小关系描述边的方向。那么,对于至少有一条路径经过的边,我们需要决定其方向,使得每一条路径上边的方向是一致的。
由此,对于任意一条路径,一旦确定其中一条边的方向,自然可以确定整条路径的方向。可以用带权并查集处理边之间方向相等和相反的关系,若出现某条边与自己方向相反,则答案显然为 1 -1

考虑用二分答案来解决剩余的最优化问题,令当前二分的答案为 k k
注意在本题中,只有存在公共点的边直接存在边之间方向相等和相反的关系,由此,可以考虑设计子树 DP 来判断答案是否可行。令 d p i dp_i 表示确定 c i < c f a t h e r i c_i<c_{father_i} 的情况下, c i c_i 可以取到的最小值。
需要说明的是,若 i i 的父边反向,可以考虑取反整个 i i 的子树,取 c i = k + 1 d p i c_i=k+1-dp_i

考虑 DP 的转移,由于我们指定了 i i 的父边的方向,因此,一部分子树连向 i i 的边的方向是固定的,这部分子树将会将 d p i dp_i 限定在一个形如 [ v , k ] [v,k] 的范围内。另一部分子树连向 i i 的边的方向与 i i 的父边的方向没有直接关联,但仍然有可能相互关联,考虑将相互关联的子树进行分组。可以发现,每一组子树将会把 d p i dp_i 限定在一个形如 [ l , r ] [ k + 1 r , k + 1 l ] [l,r]\cup[k+1-r,k+1-l] 的范围内。
DP 的转移即为合并这些区间,并找到最小的解。我们当然可以使用线段树或是扫描线来做到这一点,但若是这样,总的时间复杂度将会多出一个 O ( L o g N ) O(LogN)
线性转移的关键在于注意到 ** [ l , r ] [ k + 1 r , k + 1 l ] [l,r]\cup[k+1-r,k+1-l] 或是一个完整的区间,或是两个关于 k + 1 2 \frac{k+1}{2} 对称的区间。**换言之,若 [ l , r ] [ k + 1 r , k + 1 l ] [l,r]\cup[k+1-r,k+1-l] 中间存在缺口,则这个缺口一定包含 k + 1 2 \frac{k+1}{2} 。那么,这些缺口的并是一个区间。
因此,我们只需要维护区间的交,和缺口的并,即可做到线性转移。

同时,在转移的过程中,我们也可以找到被取反的子树,从而求出具体方案。

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

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
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;
}
int n, m; vector <int> a[MAXN];
namespace LowestCommonAncestor {
	const int MAXN = 5e5 + 5;
	const int MAXLOG = 20;
	int depth[MAXN], father[MAXN][MAXLOG];
	void work(int pos, int fa) {
		depth[pos] = depth[fa] + 1;
		father[pos][0] = fa;
		for (int i = 1; i < MAXLOG; i++)
			father[pos][i] = father[father[pos][i - 1]][i - 1];
		for (unsigned i = 0; i < a[pos].size(); i++)
			if (a[pos][i] != fa) work(a[pos][i], pos);
	}
	int lca(int x, int y) {
		if (depth[x] < depth[y]) swap(x, y);
		for (int i = MAXLOG - 1; i >= 0; i--)
			if (depth[father[x][i]] >= depth[y]) x = father[x][i];
		if (x == y) return x;
		for (int i = MAXLOG - 1; i >= 0; i--)
			if (father[x][i] != father[y][i]) {
				x = father[x][i];
				y = father[y][i];
			}
		return father[x][0];
	}
	int climb(int x, int y) {
		for (int i = 0; y != 0; i++)
			if (y & (1 << i)) {
				y ^= 1 << i;
				x = father[x][i];
			}
		return x;
	}
}
namespace UnionFind {
	const int MAXN = 1e6 + 5;
	int f[MAXN];
	void init(int n) {
		for (int i = 1; i <= n * 2; i++)
			f[i] = i;
	}
	int find(int x) {
		if (f[x] == x) return x;
		else return f[x] = find(f[x]);
	}
	void merge(int x, int y) {
		x = find(x);
		y = find(y);
		f[x] = y;
	}
	void addedge(int x, int y, bool z) {
		if (z == false) {
			merge(x, y);
			merge(x + n, y + n);
		} else {
			merge(x, y + n);
			merge(x + n, y);
		}
		if (find(x) == find(x + n)) {
			puts("-1");
			exit(0);
		}
	}
	pair <int, bool> query(int x) {
		int tmp = find(x);
		if (tmp <= n) return make_pair(tmp, false);
		else return make_pair(tmp - n, true);
	}
}
bool rev[MAXN];
int k, timer, p[MAXN];
int dp[MAXN], res[MAXN];
void getans() {
	for (int i = 1; i <= n; i++) {
		int pos = p[i];
		if (!rev[pos]) res[pos] = dp[pos];
		else res[pos] = k + 1 - dp[pos];
		for (auto x : a[pos])
			rev[x] ^= rev[pos];
	}
}
bool check(int mid) {
	k = mid;
	for (int i = n; i >= 1; i--) {
		int pos = p[i];
		vector <int> points;
		static bool trev[MAXN];
		static pair <int, int> inter[MAXN];
		using namespace UnionFind;
		for (auto x : a[pos]) {
			pair <int, bool> tmp = query(x);
			inter[tmp.first] = make_pair(0, 0);
		}
		pair <int, bool> cur = query(pos);
		points.push_back(cur.first);
		inter[cur.first] = make_pair(1, k);
		for (auto x : a[pos]) {
			pair <int, bool> tmp = query(x);
			if (inter[tmp.first].first == 0) {
				points.push_back(tmp.first);
				inter[tmp.first] = make_pair(1, k);
			}
			if (tmp.second == cur.second) chkmax(inter[tmp.first].first, dp[x] + 1);
			else chkmin(inter[tmp.first].second, k - dp[x]);
		}
		pair <int, int> ban = make_pair(k + 1, 0);
		int l = inter[cur.first].first, r = inter[cur.first].second;
		for (auto x : points) {
			pair <int, int> a = inter[x];
			pair <int, int> b = make_pair(k + 1 - a.second, k + 1 - a.first);
			if (a.first > a.second) return false;
			if (a > b) swap(a, b);
			chkmax(l, a.first);
			chkmin(r, b.second);
			if (a.second + 1 < b.first) {
				chkmin(ban.first, a.second + 1);
				chkmax(ban.second, b.first - 1);
			}
		}
		if (l > r) return false;
		if (l < ban.first || l > ban.second) dp[pos] = l;
		else if (ban.second + 1 <= r) dp[pos] = ban.second + 1;
		else return false;
		for (auto x : points) {
			if (inter[x].first <= dp[pos] && inter[x].second >= dp[pos]) trev[x] = false;
			else trev[x] = true;
		}
		for (auto x : a[pos]) {
			pair <int, bool> tmp = query(x);
			rev[x] = trev[tmp.first] ^ (tmp.second != cur.second);
		}
	}
	return true;
}
int subt[MAXN];
void build(int pos, int fa) {
	p[++timer] = pos;
	for (auto x : a[pos])
		if (x != fa) {
			build(x, pos);
			subt[pos] += subt[x];
		}
	for (unsigned i = 0; i < a[pos].size(); i++)
		if (a[pos][i] == fa) {
			swap(a[pos][i], a[pos][a[pos].size() - 1]);
			a[pos].pop_back();
			break;
		}
	if (subt[pos]) {
		UnionFind :: addedge(fa, pos, false);
	}
}
int main() {
	read(n), read(m);
	for (int i = 1; i <= n - 1; i++) {
		int x, y; read(x), read(y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	UnionFind :: init(n);
	LowestCommonAncestor :: work(1, 0);
	for (int i = 1; i <= m; i++) {
		using namespace LowestCommonAncestor;
		int x, y; read(x), read(y);
		int z = lca(x, y), p, q;
		if (x != z) {
			subt[x]++;
			subt[p = climb(x, depth[x] - depth[z] - 1)]--;
		}
		if (y != z) {
			subt[y]++;
			subt[q = climb(y, depth[y] - depth[z] - 1)]--;
		}
		if (x != z && y != z) {
			UnionFind :: addedge(p, q, true);
		}
	}
	build(1, 0);
	int l = 1, r = n;
	while (l < r) {
		int mid = (l + r) / 2;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	printf("%d\n", l);
	check(l), getans();
	for (int i = 1; i <= n; i++)
		printf("%d ", res[i]);
	printf("\n");
	return 0;
}
发布了813 篇原创文章 · 获赞 93 · 访问量 18万+

猜你喜欢

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