【CodeForces】Codeforces Round 626

比賽鏈接

點擊打開鏈接

官方題解

點擊打開鏈接

Problem A. Unusual Competitions

顯然,當且僅當左右括號的個數不相等,答案爲 1-1 。否則,將左右括號分別看做 +1,1+1,-1 ,畫出前綴和的折線圖,不難發現翻轉 xx 軸下方的部分是最優的。

時間複雜度 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

考慮分別求出答案的每一位。

若我們希望求出答案在 2i2^i 處的值,可以將所有數對 2i+12^{i+1} 取模,並排序。
此時,兩個數 x,yx,y 的和對答案產生貢獻,當且僅當 x+y[2i,2i+1)[2i+1+2i,2i+2)x+y\in[2^i,2^{i+1})\cup[2^{i+1}+2^i,2^{i+2})

用前綴和或雙指針均可解決。

時間複雜度 O(V+NLogV)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

刪去右側的孤點,它們顯然對答案沒有影響。

爲左側的每一個點隨機一個 6464 位無符號整型的權值 viv_i
令右側的每一個點的權值 bib_i 爲左側所有與其相鄰的點 viv_i 的異或和。

那麼,可以認爲,當且僅當右側兩個點的 bib_i 相同,它們始終會在 N(S)N(S) 中一起出現,或一起不出現。因此,這樣的兩個點可以被縮成一個點來看待。

可以證明,答案即爲縮點後各個點 cic_i 的 GCD 。

單組數據時間複雜度 O(M+NLogN)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

考慮將序列倒置,我們希望找出一個不降的子序列,同時最大化其關於二進制下進位的權值。

考慮一個樸素的動態規劃,記 dpi,j,kdp_{i,j,k} 表示考慮了序列的前 ii 位,當前的二進制數爲 k×2jk\times 2^j
轉移時考慮放棄當前的元素,選擇當前的元素,或進位,不難得出 O(1)O(1) 的轉移。

觀察上述 DP ,可以發現,從 dpi,j,kdp_{i,j,k}dpi+1,j,kdp_{i+1,j,k} ,產生變動的位置只有 O(N+M)O(N+M) 個,因此可以去掉第一維,僅修改這些位置,滾動數組的同時優化時間複雜度。

時間複雜度 O(NLogN×(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,00,0 和結構 1,11,1 是穩定的,因此答案應當爲最長的 0,10,1 交替的段除以 22

那麼,原問題中,考慮選取中間值 MidMid ,令 Mid\geq Mid 的元素爲 11<Mid<Mid 的元素爲 00 ,操作進行的輪數即爲所有中間值中,使得範圍在 {0,1}\{0,1\} 內的問題操作輪數最多的輪數。

由此,我們需要一個結構,支持將一個元素從 00 修改爲 11 ,同時維護當前最長的 0,10,1 交替的段的長度,並且求出修改後,最終狀態新增了那些 11
以下代碼通過維護穩定位置的方式實現了這一算法。

時間複雜度 O(NLogN)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

考慮用其兩側點 cic_i 的大小關係描述邊的方向。那麼,對於至少有一條路徑經過的邊,我們需要決定其方向,使得每一條路徑上邊的方向是一致的。
由此,對於任意一條路徑,一旦確定其中一條邊的方向,自然可以確定整條路徑的方向。可以用帶權並查集處理邊之間方向相等和相反的關係,若出現某條邊與自己方向相反,則答案顯然爲 1-1

考慮用二分答案來解決剩餘的最優化問題,令當前二分的答案爲 kk
注意在本題中,只有存在公共點的邊直接存在邊之間方向相等和相反的關係,由此,可以考慮設計子樹 DP 來判斷答案是否可行。令 dpidp_i 表示確定 ci<cfatheric_i<c_{father_i} 的情況下, cic_i 可以取到的最小值。
需要說明的是,若 ii 的父邊反向,可以考慮取反整個 ii 的子樹,取 ci=k+1dpic_i=k+1-dp_i

考慮 DP 的轉移,由於我們指定了 ii 的父邊的方向,因此,一部分子樹連向 ii 的邊的方向是固定的,這部分子樹將會將 dpidp_i 限定在一個形如 [v,k][v,k] 的範圍內。另一部分子樹連向 ii 的邊的方向與 ii 的父邊的方向沒有直接關聯,但仍然有可能相互關聯,考慮將相互關聯的子樹進行分組。可以發現,每一組子樹將會把 dpidp_i 限定在一個形如 [l,r][k+1r,k+1l][l,r]\cup[k+1-r,k+1-l] 的範圍內。
DP 的轉移即爲合併這些區間,並找到最小的解。我們當然可以使用線段樹或是掃描線來做到這一點,但若是這樣,總的時間複雜度將會多出一個 O(LogN)O(LogN)
線性轉移的關鍵在於注意到 **[l,r][k+1r,k+1l][l,r]\cup[k+1-r,k+1-l] 或是一個完整的區間,或是兩個關於 k+12\frac{k+1}{2} 對稱的區間。**換言之,若 [l,r][k+1r,k+1l][l,r]\cup[k+1-r,k+1-l] 中間存在缺口,則這個缺口一定包含 k+12\frac{k+1}{2} 。那麼,這些缺口的並是一個區間。
因此,我們只需要維護區間的交,和缺口的並,即可做到線性轉移。

同時,在轉移的過程中,我們也可以找到被取反的子樹,從而求出具體方案。

時間複雜度 O(NLogN)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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章