Loading

题解-CF1491

CF1491

场上进度:A B C D E,补题进度:G F

中国场细节多,最近又因为 AFO 连续 \(10+\) 天没写代码,比赛前也没 VP 回复过状态,只做了两三天的题(主要在划水),然后就狂暴罚时……祝贺自己成为同学中分最低的 /kk

\(*x\) 表示我 unaccepted\(x\) 发。总共:\(*9\),赛时 \(*8\)


A K-th Largest Value

维护 \(1\) 的个数即可。\(*0\)

const int xn = 1e5;
int n, m, a[xn], cnt;
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	rep(i, 0, n) cin >> a[i], cnt += a[i];
	while (m--) {
		int o, i; cin >> o >> i, --o, --i;
		if (o) cout << (i < cnt) << '\n';
		else cnt -= a[i], a[i] ^= 1, cnt += a[i];
	}
	return 0;
}

B Minimal Cost

  1. 如果所有 \(a_i\) 相等:那么一发左右移必须,然后可以上下或左右。
  2. 如果所有 \(a_i\)\(a_{i + 1}\) 的差最大为 \(1\),那么只需要一发左右或上下移。
  3. 否则,答案为 \(0\)

\(*1\):我忘了第三种情况,这就离谱。

const int xn = 100;
int n, u, v, a[xn];
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int cas; cin >> cas;
	while (cas--) {
		cin >> n >> u >> v;
		bool same = true, ok = false;
		rep(i, 0, n) cin >> a[i];
		rep(i, 0, n - 1) {
			same &= a[i] == a[i + 1];
			ok |= abs(a[i] - a[i + 1]) > 1;
		}
		int res = inf32;
		if (same) res = v + min(u, v);
		else if (ok) res = 0;
		else res = min(u, v);
		cout << res << '\n';
	}
	return 0;
}

C Pekora and Trampoline

场上直接写了 \(\Theta(n)\) 的做法。

从左到右处理,设 \(b_i\) 表示到 \(i\) 这里有 \(b_i\) 次免费跳跃机会,初始为 \(0\)

设处理到 \(i\),那么有 \(\max(0, a_i - 1 - b_i)\) 次跳跃要付费。

由于 \(i + 2\sim i + a_i\) 必然会跳到一次,所以这段 \(b\) 集体加一,可以差分实现。

然后因为有些时候免费跳跃次数过多,所以 \(i + 1\) 还会被跳到 \(\max(0, b_i - (a_i - 1))\) 次。

\(*4\):打成了 \(n^3\) 暴力(还循环写错了一发),开始差分(没考虑到跳 \(i + 1\)\(b\) 数组没清零)。

const int xn = 5000;
int n, a[xn], b[xn];

void add(int i, int x) {if (i < n) b[i] += x;}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int cas; cin >> cas;
	while (cas--) {
		cin >> n;
		i64 res = 0;
		rep(i, 0, n) cin >> a[i], b[i] = 0;
		rep(i, 0, n) {
			if (i) b[i] += b[i - 1];
			res += max(0, a[i] - 1 - b[i]);
			add(i + 1, max(0, b[i] - (a[i] - 1)));
			add(i + 2, 1 - max(0, b[i] - (a[i] - 1)));
			add(i + a[i] + 1, -1);
		}
		cout << res << '\n';
	}
	return 0;
}

D Zookeeper and The Infinite Zoo

就是你手上有一个数 \(s\),每次可以找一个 \(v\in s\) 并让 \(s += v\),问最后能不能变成 \(t\)

如果 \(s > t\) 直接不能,否则可以发现 \(s\) 的任意一个低位缀,\(1\) 的个数都不会增加。

而只要每个低位缀 \(s\) 不比 \(t\) \(1\) 少,就有解(这个操作很灵活,可以随意消 \(1\),把一段 \(1\) 集体高移)。

\(*2\):打表打错了找出了错规律,又写出了错的贪心。

bool check(int u, int v) {
	if (u > v) return false;
	int uc = 0, vc = 0;
	rep(i, 0, 30) {
		if (u >> i & 1) ++uc;
		if (v >> i & 1) ++vc;
		if (uc < vc) return false;
	}
	return true;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int cas; cin >> cas;
	while (cas--) {
		int x, y;
		cin >> x >> y;
		if (check(x, y)) cout << "YES\n";
		else cout << "NO\n";
	}
	return 0;
}

E Fib-tree

先判是否存在 \(n = fib_k\),听说不判会 FST

由于 \(k - 1\) 级子树必过重心,所以 \(k - 2\) 级子树是重心为根的两个子树,所以最多有 \(2\) 种符合数量的分法。

需要证明,如果整棵树是有解的,两种分法是等价的:

如果有 \(2\) 种分法,树结构必然如下(重心在红块里):

image.png

由于 \(k \le 3, n \le 3\) 都有解,所以如果 \(k \le 5, n \le 8\),这个结论必然成立。

那么可以假设对于树级 \(i \in [0, k)\),我们已经证明这个结论成立。

那么无论分左边还是分右边,把 \(k - 1\) 级子树再分后都和两条边同时割等价。

所以对于树级 \(k\) 结论成立,递推得对于所有树级 \(i\) 结论成立。

那么每次 \(\Theta(fib_k)\) 找边然后分治即可,时间复杂度 \(\Theta(n\log n)\)

\(*2\):找边找漏了,全局变量在递归中修改然后出来后调用导致出错。

const int xn = 2e5 + 1, xf = 27;
int n, fib[xf], id[xn];

void init() {
	fib[0] = fib[1] = 1;
	rep(i, 2, xn + 1) id[i] = -1;
	rep(i, 2, xf){
		fib[i] = fib[i - 1] + fib[i - 2];
		id[fib[i]] = i;
	}
}

vector<int> adj[xn];
bool vis[xn];
int si[xn], eu, ev, k;

void finde(int u, int fa) {
	si[u] = 1;
	for (int v : adj[u]) {
		if (vis[v] or v == fa) continue;
		finde(v, u), si[u] += si[v];
	}
	if (si[u] == fib[k - 2] and !~eu) eu = u, ev = fa; 
	if (si[u] == fib[k - 1] and !~eu) eu = fa, ev = u;
}

bool dfs(int u) {
	if (k <= 3) return true;
	eu = -1, ev = -1, finde(u, -1); int a = eu, b = ev;
	if (!~eu) return false;
	bool res = true;
	vis[a] = true, --k, res &= dfs(b), vis[a] = false, ++k;
	vis[b] = true, k -= 2, res &= dfs(a), vis[b] = false, k += 2;
	return res;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	init(), cin >> n;
	rep(i, 0, n - 1) {
		int u, v; cin >> u >> v, --u, --v;
		adj[u].push_back(v), adj[v].push_back(u);
	}
	if (n <= 2) return cout << "YES\n", 0; 
	if (!~id[n]) return cout << "NO\n", 0;
	if (k = id[n], dfs(0)) cout << "YES\n";
	else cout << "NO\n";
	return 0;
}

F Magnets

先化一下式子:\(F = (n_1 - s_1)(n_2 - s_2)\)

很明显如果得到了一个有磁性的磁铁,把它和别的比较一下次数 \(n - 1\)

可是这个东西并不能直接二分,所以只能一个一个枚举。

这里需要注意:一个一个枚举,只要让枚举失败也对答案也有贡献,就不会崩盘。

题目还说绝对值 \(\le n\),这也很有提示性,说明需要 \(\Theta(n)\) 个和 \(1\) 个为一组的查询。

所以可以从左到右,每次把 \(1 \sim i - 1\)\(i\) 查询一下。

那么当 \(i\) 是第二颗有磁性的石头的时候,第一次 \(F \neq 0\)

这时候 \(1 \sim i - 1\) 有恰好一颗磁石,正好可以利用 \(i\) 二分。

然后 \(i + 1 \sim n\) 每个都可以用 \(1\) 次查询得知是否有磁性。

操作次数最多为 \(n - 1 + \lceil\log_2 n\rceil \le n + \lfloor\log_2 n\rfloor\)

时间复杂度 \(\Theta(n^2)\)

\(*0\)Good job

const int N = 2000;
int n, F, nd, d[N];

void Solve() {
	cin >> n, nd = 0;
	int s = -1;
	rep(i, 1, n) {
		cout << "? 1 " << i << '\n';
		cout << i + 1 << '\n';
		rep(j, 0, i) cout << j + 1 << ' ';
		cout << endl;
		cin >> F;
		if (F) {
			s = i;
			break;
		}
	}
	int l = 0, r = s;
	while (r - l > 1) {
		int mid = (l + r) / 2;
		cout << "? 1 " << mid - l << '\n';
		cout << s + 1 << '\n';
		rep(i, l, mid) cout << i + 1 << ' ';
		cout << endl;
		cin >> F;
		if (F) r = mid;
		else l = mid;
	}
	rep(i, 0, l) d[nd++] = i;
	rep(i, l + 1, s) d[nd++] = i;
	rep(i, s + 1, n) {
		cout << "? 1 1\n";
		cout << i + 1 << '\n';
		cout << s + 1 << endl;
		cin >> F;
		if (!F) d[nd++] = i;
	}
	cout << "! " << nd << ' ';
	rep(i, 0, nd) cout << d[i] + 1 << ' ';
	cout << endl;
}

G Switch and Flip

先转化为一张 \(n\) 个点由 \((i, p_i)\) 构成的有向图。

设正面朝上的点是红的,否则是蓝的,一次操作交换两个点的出点并翻转 \(2\)出点的颜色。

然后这题巧妙的地方就在于把两个环一起消比单独消一个环更优。

  1. 两个长度为 \(a, b\) 的环消掉耗费 \(a + b\) 步。
  2. 一个长度为 \(a > 2 : a\) 的环自己消掉耗费 \(a + 1\) 步。

具体操作(点击展开五彩斑斓的图图 /se)。

image.png

所以可以用 1 消得至多只剩一个环。

如果这个环有两个点,那么必然有不在这个环中的点,可以把这个环和那个点合并共 \(n + 1\) 步。

否则用 2 把这个环消掉即可。

\(*1\):把上文加粗部分看成了这两个点本身翻转颜色,然后做了一遍。

const int xn = 2e5 + 1;
int n, p[xn], q[xn], no, o[xn][2];
bool vis[xn];
 
void flip(int i, int j) {
	swap(p[i], p[j]), swap(q[p[i]], q[p[j]]);
	o[no][0] = i, o[no][1] = j, ++no;
}
 
void offset(int i, int j) {
	flip(i, j), i = p[i], j = p[j];
	while (p[i] != j) i = p[i], flip(q[i], q[q[i]]);
	while (p[j] != i) j = p[j], flip(q[j], q[q[j]]);
	flip(i, j);
}
 
void solone(int k) {
	rep(i, 0, n) if (i == p[i]) return offset(i, k);
	int i = k; k = p[k], flip(q[k], q[q[k]]);
	while (p[k] != q[k]) k = p[k], flip(q[k], q[q[k]]);
	flip(i, k), flip(k, q[k]), flip(i, p[i]);
}
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n;
	rep(i, 0, n) cin >> p[i], q[--p[i]] = i;
	int k = -1;
	rep(i, 0, n) if (not vis[i]) {
		if (p[i] == i) continue;
		while (not vis[i]) vis[i] = true, i = p[i];
		if (!~k) k = i;
		else offset(i, k), k = -1;
	}
	if (~k) solone(k);
	cout << no << '\n';
	rep(i, 0, no) cout << o[i][0] + 1
		<< ' ' << o[i][1] + 1 << '\n';
	return 0;
}

H Yuezheng Ling and Dynamic Tree


I Ruler Of The Zoo


濒临退役。

posted @ 2021-03-02 11:40  George1123  阅读(162)  评论(0编辑  收藏  举报