zoukankan      html  css  js  c++  java
  • 题解-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) << '
    ';
    		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 << '
    ';
    	}
    	return 0;
    }
    

    C Pekora and Trampoline

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

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

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

    由于 (i + 2sim 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 << '
    ';
    	}
    	return 0;
    }
    

    D Zookeeper and The Infinite Zoo

    就是你手上有一个数 (s),每次可以找一个 (vin 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
    ";
    		else cout << "NO
    ";
    	}
    	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(nlog 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
    ", 0; 
    	if (!~id[n]) return cout << "NO
    ", 0;
    	if (k = id[n], dfs(0)) cout << "YES
    ";
    	else cout << "NO
    ";
    	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 eq 0)

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

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

    操作次数最多为 (n - 1 + lceillog_2 n ceil le n + lfloorlog_2 n floor)

    时间复杂度 (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 << '
    ';
    		cout << i + 1 << '
    ';
    		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 << '
    ';
    		cout << s + 1 << '
    ';
    		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
    ";
    		cout << i + 1 << '
    ';
    		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 << '
    ';
    	rep(i, 0, no) cout << o[i][0] + 1
    		<< ' ' << o[i][1] + 1 << '
    ';
    	return 0;
    }
    

    H Yuezheng Ling and Dynamic Tree


    I Ruler Of The Zoo


    濒临退役。

  • 相关阅读:
    Win10桌面点右键一直卡顿转圈怎么解决
    Ubuntu 中检查笔记本 CPU 的温度
    aria2的安装与配置
    Downloading Vim
    如何在Ubuntu 18.04上安装Python 3.8
    理解 chroot
    Ubuntu快捷方式存放的位置
    安装ubuntu双系统后,找不到windows启动项的解决方法
    socks5 协议简介
    mybatis源码1.3 MapperMethod
  • 原文地址:https://www.cnblogs.com/George1123/p/14468152.html
Copyright © 2011-2022 走看看