Codeforces Round 1016 (Div. 3)


A. Ideal Generator

题意:给你\(k\),求能不能使得所有的\(n \leq k\)\(n\),都能构造一个全部非零的长度为\(k\)的总和为\(n\)的回文数组。

如果\(k\)是奇数,都放\(1\)然后剩下的给中间就行了。
如果是偶数,无解。

点击查看代码
void solve() {
	int n;
	std::cin >> n;
	if (n & 1) {
		std::cout << "YES\n";
	} else {
		std::cout << "NO\n";
	}
}

B. Expensive Number

题意:\(n\)的价值为\(n\)除数位和。你可以删去\(n\)的一些数,使得它严格大于\(0\),可以有前导零。使得价值最小需要删几个数。

显然最小价值为\(1\)。那么如果我们留下一个非零数,那么它前面的\(0\)可以保留。求前面零最多的数。

点击查看代码
void solve() {
    std::string s;
    std::cin >> s;
    int n = s.size();
    int ans = n - 1;
    int cnt = 0;
    for (int i = 0; i < n; ++ i) {
    	if (s[i] != '0') {
    		ans = std::min(ans, n - 1 - cnt);
    	} else {
    		++ cnt;
    	}
    }

    std::cout << ans << "\n";
}

C. Simple Repetition

题意:把\(n\)复制\(k\)次形成一个数字,求这个数字是不是质数。

如果\(n\)不是\(1\)\(k>1\),肯定不是质数,因为\(nn...nn\)显然可以整除\(n\)
那么如果\(n\)\(1\),复制\(k\)遍判断,其它情况就是\(k=1\),也是直接判断是不是质数。

点击查看代码
void solve() {
    int n, k;
    std::cin >> n >> k;
    if (n != 1 && k > 1) {
    	std::cout << "NO\n";
    } else {
    	if (n == 1) {
    		while ( -- k) {
    			n = n * 10 + 1;
    		}
    	}

    	if (n == 1) {
    		std::cout << "NO\n";
    		return;
    	}
    	for (int i = 2; i <= n / i; ++ i) {
    		if (n % i == 0) {
    			std::cout << "NO\n";
    			return;
    		}
    	}
    	std::cout << "YES\n";
    }
}

D. Skibidi Table

题意:一个\(2^n \times 2^n\)的矩阵,把\([1, 2^n \times 2^n]\)的数放进去,把数组分成四部分。顺序为先放左上角,然后放右下角,再放左下角,最后放右上角。这是一个递归,因为每一部分是一个\(2^{n-1} \times 2^{n-1}\)的矩阵,这个矩阵也按照这个规律放置。现在有两种询问,一种是问\((x, y)\)这个位置是哪个数,一种是问\(d\)的坐标。

题目已经很明显提示这是递归了。
对于第一种询问,我们递归求解,\(dfs(n, x, y)\)表示\(2^n \times 2^n\)的矩阵求\((x, y)\)这个位置的数。那么记\(m = 2^{n-1}\),如果\(x \leq m, y \leq m\),则在左上角,答案是\(dfs(n - 1, x, y)\)。如果\(x > m, y > m\),那么在右下角,答案是\(dfs(n - 1, x - m, y - m) + m^2\),意味把它放缩到\(2^{n-1} \times 2^{n-1}\)的矩阵里下标是\((x-m, y-m)\),同时要加上左下角的数。那么同理讨论,如果\(x > m, y leq m\),则在左下角,答案为\(dfs(n - 1, x - m, y) + 2 \times m^2\)。否则答案为\(dfs(n - 1, x, y - m) + 3 \times m^2\)。边界就是\(n=0\)返回\(1\)
第二种询问同理讨论,\(dfs(n, d)\)表示\(2^n \times 2^n\)的矩阵求\(d\)的坐标,那么讨论在哪个部分,同时减去相应的值,得到下标后加上对应的值。
具体参考代码。

点击查看代码
void solve() {
    i64 n, q;
    std::cin >> n >> q;
    auto work1 = [&](auto & self, i64 n, i64 x, i64 y) -> i64 {
    	if (n == 0) {
    		return 1;
    	}
    	i64 m = 1ll << n - 1;
    	if (x <= m && y <= m) {
    		return self(self, n - 1, x, y);
    	} else if (x > m && y > m) {
    		return self(self, n - 1, x - m, y - m) + m * m;
    	} else if (x > m && y <= m) {
    		return self(self, n - 1, x - m, y) + m * m * 2;
    	} else {
    		return self(self, n - 1, x, y - m) + m * m * 3;
    	}
    };

    auto work2 = [&](auto & self, i64 n, i64 d) -> std::pair<i64, i64> {
    	if (n == 0) {
    		return {1, 1};
    	}

    	i64 m = 1ll << n - 1;
    	if (d <= m * m) {
    		return self(self, n - 1, d);
    	} else if (d <= 2 * m * m) {
    		auto [x, y] = self(self, n - 1, d - m * m);
    		x += m; y += m;
	    	return {x, y};
    	} else if (d <= 3 * m * m) {
    		auto [x, y] = self(self, n - 1, d - 2 * m * m);
    		x += m;
	    	return {x, y};
    	} else {
    		auto [x, y] = self(self, n - 1, d - 3 * m * m);
    		y += m;
	    	return {x, y};
    	}
    };

    while (q -- ) {
    	std::string op;
    	std::cin >> op;
    	if (op[0] == '-') {
    		i64 x, y;
    		std::cin >> x >> y;
    		std::cout << work1(work1, n, x, y) << "\n";
    	} else {
    		i64 d;
    		std::cin >> d;
    		auto [x, y] = work2(work2, n, d);
    		std::cout << x << " " << y << "\n";
    	}
    }
}

E. Min Max MEX

题意:把\(a\)分成\(k\)份,价值为\(\min_{i=1}^{k} (mex(b_i))\),求最大价值。

考虑二分,如果\(x\)可行,那么我们从前往后遍历求\(mex\),一直到\(mex\)大于等于\(x\)停止,那么如果能得到至少\(k\)份就是可以的。因为后面多出来的直接给最后一部分不会减少这一部分的\(mex\)

upd:用\(map\)双log被叉了。\(check\)里我们开个数组记录每个数出现没有就行,每次找到一段把这一段的数的贡献消去就行了。这样每个数最多被遍历两次。

点击查看代码
void solve() {
    int n, k;
    std::cin >> n >> k;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    if (n == k) {
    	std::cout << (std::ranges::count(a, 0) == n) << "\n";
    	return;
    }

    auto check = [&](int x) -> bool {
    	int cnt = 0;
    	std::vector<int> st(x + 1);
    	for (int i = 0; i < n; ++ i) {
    		int mex = 0;
    		int j = i;
    		for (; j < n && mex < x; ++ j) {
	    		if (a[j] < x) {
	    			st[a[j]] = 1;
	    		}
	    		while (st[mex]) {
	    			++ mex;
	    		}
    		}

    		cnt += mex >= x;
    		for (int k = i; k < j; ++ k) {
    			if (a[k] < x) {
    				st[a[k]] = 0;
    			}
    		}
    		i = j - 1;
    	}

    	return cnt >= k;
    };

    int l = 0, r = n;
    while (l < r) {
    	int mid = l + r + 1 >> 1;
    	if (check(mid)) {
    		l = mid;
    	} else {
    		r = mid - 1;
    	}
    }

    std::cout << l << "\n";
}

F. Hackers and Neural Networks

这题不给样例解释,题目看半天。

题意:你要得到数组\(a\),一开始你有一个长度为\(n\)元素都为空的数组\(c\)。给你\(m\)个长度为\(n\)的数组\(b_i\)。你可以进行若干个操作,每次选择其中一种:

  1. 选择一个\(i\),那么会随机选择一个空位\(j\),使得\(c[j] = b[i][j]\)
  2. 选择一个\(i\),然后在选择一个\(j\),使得\(c[j]\)为空。

因为是随机的,所有我们不能保证我们得到想要的。但我们可以对一个数组进行\(n\)次第一种操作,那么就变成了这个数组,然后我们把错误的位置变成空,再和其它数组进行第一个操作。
那么我们枚举一开始得到的数组,一开始操作\(n\)次。然后每次贪心找剩下可以得到最多正确位置的\(b_i\),假设有\(cnt\)个,那么我们把这\(cnt\)个位置变成空,然后操作\(cnt\)次进行,一共操作\(2\times cnt\)次。
一直到所有位置都满足条件就行了。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<std::string> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::vector b(m, std::vector<int>(n));
    for (int i = 0; i < m; ++ i) {
    	for (int j = 0; j < n; ++ j) {
    		std::string s;
    		std::cin >> s;
    		b[i][j] = s == a[j];
    	}
    }

    auto work = [&](int i) -> int {
    	int sum = 0;
	    std::set<int> s;
	    for (int i = 0; i < n; ++ i) {
	    	s.insert(i);
	    }

	    auto get = [&]() -> std::vector<int> {
	    	int max = 0, id = 0;
	    	for (int i = 0; i < m; ++ i) {
	    		int cnt = 0;
	    		for (auto & j : s) {
	    			cnt += b[i][j];
	    		}

	    		if (cnt > max) {
	    			max = i;
	    			id = i;
	    		}
	    	}

	    	std::vector<int> res;
	    	for (auto & i : s) {
	    		if (b[id][i]) {
	    			res.push_back(i);
	    		}
	    	}

	    	return res;
	    };

    	for (int j = 0; j < n; ++ j) {
    		if (b[i][j]) {
    			s.erase(j);
    		}
    	}

    	sum = n;
	    while (s.size()) {
	    	auto c = get();
	    	if (c.empty()) {
	    		return - 1;
	    	}

	    	for (auto & x : c) {
	    		s.erase(x);
	    	}

	    	sum += (int)c.size() * 2;
	    }

	    return sum;
    };
    
    int ans = -1;
    for (int i = 0; i < m; ++ i) {
    	int v = work(i);
    	if (ans == -1 || v < ans) {
    		ans = v;
    	}
    }

    std::cout << ans << "\n";
}

G. Shorten the Array

题意:给你一个数组\(a\),求一对数\((i, j)\),使得\(a_i \oplus a_j \geq k\)\(j - i + 1\)最小。

不明白为什么\(g\)是一个\(01trie\)板子。
我们给\(01trie\)每个节点记录一个最大下标,表示他这个节点的前缀可以表示的最后的\(a_i\)。那么我们从前往后插入,这个下标是递增的,直接更改就行。
然后就是查询,对于\(a_i\),我们要求一个\(a_j \oplus a_i \leq k\)\(j\)最大,那么从高位开始考虑,如果这一位\(a_i\)\(x\)\(k\)\(y\),如果\(y=1\),那么跳到\(a\oplus 1\)这个子树,否则我们可以在一位大于\(k\),用\(a \oplus 1\)这棵子树的最大下标更新答案。然后跳到\(a\)这棵子树,和\(k\)保持一个相等的前缀。这样就能得到最后边的\(j\)

点击查看代码
const int N = 2e5 + 5;

int trie[N * 30][2];
int node_max[N * 30];
struct TrieWith01 {
	int idx;
	int creat() {
		memset(trie[idx], 0, sizeof trie[idx]);
		node_max[idx] = -1;
		return idx ++ ;
	}

	TrieWith01() {
		idx = 0;
		creat();
	}

	void insert(int x, int id) {
		int p = 0;
		for (int i = 30; i >= 0; -- i) {
			int s = x >> i & 1;
			if (!trie[p][s]) {
				trie[p][s] = creat();
			}

			p = trie[p][s];
			node_max[p] = id;
		}
	}
	int query(int x, int k) {
		int p = 0;
		int res = -1;
		for (int i = 30; i >= 0; -- i) {
			int a = x >> i & 1, b = k >> i & 1;
			if (b == 1) {
				p = trie[p][a ^ 1];
			} else {
				res = std::max(res, node_max[trie[p][a ^ 1]]);
				p = trie[p][a];
			}

			if (p == 0) {
				return res;
			}
		}

		res = std::max(res, node_max[p]);

		return res;
	}
};

void solve() {
    int n, k;
    std::cin >> n >> k;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    TrieWith01 tr;
    int ans = -1;
    for (int i = 0; i < n; ++ i) {
    	tr.insert(a[i], i);
    	int j = tr.query(a[i], k);
    	if (j != -1) {
    		if (ans == -1 || i - j + 1 < ans) {
    			ans = i - j + 1;
    		}
    	}
    }

    std::cout << ans << "\n";
}
posted @ 2025-04-09 01:23  maburb  阅读(607)  评论(6)    收藏  举报