刷CF #1900
CF1114D Flood Fill
题目描述
给定一排 \(n\) 个彩色方格,从左到右编号为 \(1\) 到 \(n\)。第 \(i\) 个方格初始颜色为 \(c_i\)。
我们称,如果 \(c_i = c_j\),且对于所有满足 \(i < k < j\) 的 \(k\),都有 \(c_i = c_k\),那么第 \(i\) 个方格和第 \(j\) 个方格属于同一个连通块。换句话说,从 \(i\) 到 \(j\) 的所有方格颜色都相同。
例如,序列 \([3, 3, 3]\) 只有 \(1\) 个连通块,而序列 \([5, 2, 4, 4]\) 有 \(3\) 个连通块。
现在在这排方格上玩“洪水填充”游戏,规则如下:
- 游戏开始时,你可以任选一个起始方格(这一步不计入回合数)。
- 每一回合,你可以将包含起始方格的连通块的颜色更改为任意其他颜色。
请你求出,最少需要多少回合,才能将整排方格变成同一种颜色。
题解
首先,我们在读入时就把相同颜色的连续段缩成一个点,易知不会影响答案。
然后我们设 \(f_{i,j}\) 表示把 \(i \to j\) 的所有点变成同一个颜色所需的操作数。然后有两种情况:
- 若 \(col_i = col_j\),则我们可以用 \(f_{i+1,j-1}\) 步把内部区间变成同一种颜色,然后加一步变成和两端一样的颜色,则 \(f_{i,j} = f_{i + 1, j - 1} + 1\)。
- 若 \(col_i \neq col_j\),则最终的颜色只可能与左端一样或右端一样,如果我们最终想要左端颜色,则必须先花 \(f_{i + 1, j}\) 步将右段统一成左端颜色,然后额外花一步把整个区间染成该颜色;反之亦然。两者取最小再加 \(1\) 即可。转移方程 \(f_{i,j} = \min(f_{i,j-1},f_{i+1, j}) + 1\)。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 5005;
int f[M][M], col[M], cnt;
int main(){
int n, lst;
scanf("%d", &n);
for(int i = 1; i <= n; i++){
int x;
scanf("%d", &x);
if(x != lst) col[++cnt] = x;
lst = x;
}
for (int l = 1; l <= cnt; l++){
for (int i = 1; i + l <= cnt; i++) {
int r = i + l;
if (col[i] == col[r]) f[i][r] = f[i + 1][r - 1] + 1;
else f[i][r] = min(f[i + 1][r], f[i][r - 1]) + 1;
}
}
printf ("%d\n", f[1][cnt]);
return 0;
}
CF2013D Minimize the Difference
题目描述
给你一个长度为 \(n\) 的数 \(a_1,a_2,…,a_n\), 我们可以对数组进行任意数量(可能是零)次的操作。
在每次操作中,可以选择一个位置 \(i~(1 \le i \le n−1)\),使 \(a_i-1,a_{i+1}+1\)。
求 \(max(a_1,a_2,…,a_n)−min(a_1,a_2,…,a_n)\) 的最小可能值。
题解
注意到直接求解很难做,然后又是最小,考虑二分。
我们就可以发现操作的本质其实就是把前面的数搬到后面。所以有以下 HINT:
定义前缀和 \(s_i = \sum\limits_{j=1}^i a_j\),若最终数组为 \(\{b\}\) ,其前缀和 \(r_i = \sum\limits_{j=1}^i b_j\),则数组 \(b\) 必须满足:\(\sum\limits_{i=1}^n a_i = \sum\limits_{i=1}^n b_i\) 且 \(\forall i \in [1,n], ~r_i \le s_i\)。
二分答案,需要判断能否存在极差为 \(d\) 的数组。
假设 \(L = \min\limits_{i=1}^n b_i\),则数组内的每个数范围在 \([L, L + d]\) 之间。对于 \(i \in [1, n]\),都必须有 \(i \times L\le r_i \le s_i\),所以 \(\max_L = \min\limits_{i = 1}^n \lfloor \frac{s_i}{i} \rfloor\)。
同时,对于 \(i \in [0,n-1]\),有 \((n−i)(L+d) \geq s_n - s_i\),由此得到 \(\min_L = \max\limits_{i=1}^n (\lceil \frac{s_n - s_i}{n-i}\rceil-d)\)。
所以只要 \(\min_L \le max_L\),当前 \(d\) 就是合法的。
Code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
ll a[M], tot;
int n;
bool chk(ll x){
ll mx = 1e18;
ll pre = 0;
for (int i = 1; i <= n; ++i) {
pre += a[i];
mx = min(mx, pre / i);
}
ll mn = -1e18;
pre = 0;
for (int i = 0; i < n; ++i) {
if (i > 0) pre += a[i];
ll c = (tot - pre + (n - i) - 1) / (n - i);
mn = max(mn, c - x);
}
return mn <= mx;
}
void sol(){
scanf("%d", &n);
tot = 0;
for(int i = 1; i <= n; i++){
scanf("%lld", &a[i]);
tot += a[i];
}
ll l = 0, r = 1e12, ans;
while(l <= r){
ll mid = (l + r) >> 1;
if(chk(mid)){
ans = mid, r = mid - 1;
} else l = mid + 1;
}
printf("%lld\n", ans);
}
int main(){
int T;
scanf("%d", &T);
while(T--) sol();
return 0;
}
CF222E Decoding Genome
题目描述
最近,对火星的一次秘密研究拉开帷幕,在这次探寻中,科学家们发现了火星上的 DNA。
他们发现,火星 DNA 包含 \(n\) 个核苷酸,且由 \(m\) 种不同核苷酸构成(编号为 \(1\)~\(m\))。
他们还发现出于某些特殊的原因,存在 \(k\) 对核苷酸,一对这样的核苷酸不会连续出现在火星 DNA 中。
例如存在一对这样的核苷酸 \((1,2)\),则核苷酸 \(2\) 不能紧连着核苷酸 \(1\)(即 DNA 中不会连续出现 \(1,2\));但是可以连续出现 \(2,1\)。
科学家们想知道有多少种不同的火星 DNA(如果两个 DNA 存在一个位置,使得两个 DNA 的该位置的核苷酸不同,则认为这两个 DNA 不同)。
题解
像这种字符串的题目应该想到 dp。
我们设 \(dp_{i,j}\) 表示到了前 \(i\) 位,第 \(i\) 位填 \(j\) 的方案数。我们有转移:\(dp_{i,j} = \sum\limits_{i = 1}^m dp_{i-1, j} \times [valid_{i,j}]\)。
注意到每一个位置上都是乘以 \(valid_{i,j}\),不难想到使用矩阵快速幂加速 dp 过程。时间复杂度 \(\mathcal{O}(m^3 \log n)\)。注意初始化 \(dp_{1,i} = 1\)。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int M = 78;
int valid[M][M];
int dec(char x){
if(x >= 'a' && x <= 'z') return x - 'a' + 1;
else if(x >= 'A' && x <= 'Z') return x - 'A' + 27;
return -1;
}
struct mat{
int arr[M][M];
};
mat mul(mat a, mat b){
mat c;
memset(c.arr, 0, sizeof(c.arr));
for(int k = 1; k <= 60; k++){
for(int i = 1; i <= 60; i++){
for(int j = 1; j <= 60; j++){
c.arr[i][j] = (c.arr[i][j] + a.arr[i][k] * b.arr[k][j]) % mod;
}
}
}
return c;
}
mat qpow(mat x, int y){
mat res;
for(int i = 1; i <= 60; i++){
for(int j = 1; j <= 60; j++){
res.arr[i][j] = (i == j);
}
}
if(y == 1) return res;
while(y){
if(y & 1) res = mul(res, x);
x = mul(x, x);
y >>= 1;
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m, k;
cin >> n >> m >> k;
mat base, ans;
for(int i = 1; i <= m; i++){
for(int j = 1; j <= m; j++) {
base.arr[i][j] = 1;
}
}
for(int i = 1; i <= k; i++){
string s;
cin >> s;
int b1 = dec(s[0]), b2 = dec(s[1]);
base.arr[b1][b2] = 0;
}
for(int i = 1; i <= m; i++){
ans.arr[1][i] = 1;
}
ans = mul(ans, qpow(base, n - 1));
int res = 0;
for(int i = 1; i <= m; i++){
res = (res + ans.arr[1][i]) % mod;
}
cout << res;
return 0;
}
CF119B Before Exam
题目描述
Vasya 即将在几分钟后参加他的第一次大学考试。而且这不仅仅是一场普通的考试,而是数学分析考试。当然,现在 Vasya 只想着一件事:他和考官的谈话结果会如何……
为了准备考试,需要学习 \(n\) 个定理的证明。已知考试时会有 \(k\) 张试题卡,每张卡包含 \(\left\lfloor \frac{n}{k} \right\rfloor\) 个不同的定理。此外,没有任何一个定理会出现在多于一张卡片上(也就是说,\(n \bmod k\) 个定理不会出现在任何卡片上)。考试时,可能有多个学生抽到同一张卡片。
我们并不知道定理在卡片上的具体分布,但在 Vasya 之前参加考试的学生告诉了他他们卡片上包含了哪些定理。Vasya 用某个数 \(a_i\) 来表示他对第 \(i\) 个定理的掌握程度。一张卡片的掌握程度是该卡片上所有定理掌握程度的平均值。现在,Vasya 想知道他在考试中可能抽到的卡片的最低和最高掌握程度。Vasya 想根据他从其他学生那里收集到的数据来判断这一点。但他已经没有时间做计算了,于是请你帮他解决这个问题。
题解
简单贪心。显然最小值是剩余中最小的 \(m\) 个,最大值是剩余中最大的 \(m\) 个。模拟即可。这有 1900?
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 105;
int a[M];
bool vis[M];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, q, m, k;
cin >> n >> k;
m = n / k;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
cin >> q;
vector<vector<int>> v;
double minn = 1e9, maxn = -1e9;
for(int i = 1; i <= q; i++){
vector<int> c(m);
for(int j = 0; j < m; j++){
cin >> c[j];
}
sort(c.begin(), c.end());
bool f = 0;
for(auto &x : v){
if(x == c){
f = 1;
break;
}
}
if(!f){
v.push_back(c);
double sum = 0;
for(int x : c){
sum += a[x];
vis[x] = 1;
}
minn = min(minn, sum / m);
maxn = max(maxn, sum / m);
}
}
vector<int> b;
for(int i = 1; i <= n; i++){
if(!vis[i]) b.push_back(a[i]);
}
sort(b.begin(), b.end());
if(v.size() < k){
double mn = 0, mx = 0;
for(int i = 0; i < m; i++){
mn += b[i];
mx += b[b.size() - 1 - i];
}
minn = min(minn, mn / m);
maxn = max(maxn, mx / m);
}
cout << fixed << setprecision(10) << minn << " " << maxn;
return 0;
}
CF1561D2 Up the Strip
题目描述
你有一个由 \(n\) 个格子组成的竖直条带,这些格子从上到下依次编号为 \(1\) 到 \(n\)。
你还有一个棋子,初始时放在第 \(n\) 个格子上。你需要不断地将棋子向上移动,直到它到达第 \(1\) 个格子。
假设某一时刻棋子位于第 \(x\) 个格子(\(x > 1\))。每次移动棋子可以有以下两种方式之一:
- 减法操作:你选择一个整数 \(y\),满足 \(1 \le y \le x-1\),然后将棋子从第 \(x\) 个格子移动到第 \(x-y\) 个格子。
- 向下取整除法操作:你选择一个整数 \(z\),满足 \(2 \le z \le x\),然后将棋子从第 \(x\) 个格子移动到第 \(\lfloor \frac{x}{z} \rfloor\) 个格子(即 \(x\) 除以 \(z\) 向下取整)。
请你计算,将棋子从第 \(n\) 个格子移动到第 \(1\) 个格子的所有不同方案数(每次至少要移动一次),并将结果对 \(m\) 取模后输出。注意,如果存在多种方式能在一次移动中将棋子从一个格子移动到另一个格子,则这些方式都视为不同的方案(参见样例解释以加深理解)。
题解
我们注意到我们不能枚举了 \(f_i\) 的转移方向了,正难则反,我们考虑 \(f_i\) 通过两种操作转移来的贡献。
第一个操作还是一样的后缀和。假设后缀和数组为 \(sum\) 。但第二个操作需要分情况:假设是通过除以 \(j\) 转移来的,显然答案需要加上 \(sum_{j \times i + j - 1} - sum_{j \times i}\),也就是 \(j\) 的 \(i\) 到 \(i+1\) 倍之间。
显然复杂度为 \(\mathcal{O}(n \ln n)\),可以通过加强版。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 4e6 + 6;
int f[M], sum[M];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
f[n] = sum[n] = 1;
for (int i = 1; i <= n - 1; i++) f[i] = 0;
for (int i = n - 1; i >= 1; i--) {
f[i] = (f[i] + sum[i + 1]) % m;
for (int j = 2; i * j <= n; j++) {
f[i] = (f[i] + sum[i * j] - sum[min(i * j + j, n + 1)]) % m;
f[i] = (f[i] + m) % m;
}
sum[i] = (sum[i + 1] + f[i]) % m;
}
cout << f[1];
return 0;
}
CF2022D1 Asesino (Easy Version)
题目描述
有 \(n\) 名玩家(编号从 \(1\) 到 \(n\))将参与“Asesino”游戏,游戏中有以下三种角色:
- 骑士(Knight):骑士总是说真话。
- 恶棍(Knave):恶棍总是说谎话。
- 冒名顶替者(Impostor):冒名顶替者被所有人认为是骑士,但实际上是恶棍。
每个玩家都会被分配一个角色。游戏中恰好有一名冒名顶替者,但骑士和恶棍的人数可以为任意(可能为零)。你需要找出谁是冒名顶替者。
为了确定冒名顶替者,你可以提出一些问题。每次提问,你可以选择两名玩家 \(i\) 和 \(j\)(\(1 \leq i, j \leq n\),\(i \neq j\)),询问玩家 \(i\) 是否认为玩家 \(j\) 是骑士。问题的结果如下表所示:
| 骑士 | 恶棍 | 冒名顶替者 | |
|---|---|---|---|
| 骑士 | 是 | 否 | 是 |
| 恶棍 | 否 | 是 | 否 |
| 冒名顶替者 | 否 | 是 | — |
表中第 \(a\) 行第 \(b\) 列的单元格表示当 \(i\) 的角色为 \(a\),\(j\) 的角色为 \(b\) 时的回答。
你需要在最多 \(n+69\) 次提问内找出冒名顶替者。
题解
我们发现这个冒名顶替者其实是恶棍,但别人都认为他是骑士,说明他的回答与其他人相反,凭借这一点我们就可以用最多 \(n\) 次询问确定在两人之间。
然后因为冒名顶替者只有一个,所以随便拉一个不是这两人的第三者询问即可区分。最劣询问 \(n+2\) 次,轻松满足限制。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int qry(int x, int y) {
int res;
cout << "? " << x << " " << y << endl;
cin >> res;
return res;
}
void ex(int x) {
cout << "! " << x << endl;
}
int sol() {
int n;
cin >> n;
for (int i = 2; i <= n; i += 2) {
if (qry(i, i - 1) != qry(i - 1, i)) {
if (i != 2) {
if (qry(i, 1) != qry(1, i)) return i;
} else if (qry(i, n) != qry(n, i))return i;
if (i != 2) {
if (qry(i - 1, 1) != qry(1, i - 1))return i - 1;
} else if (qry(i - 1, n) != qry(n, i - 1))return i - 1;
}
}
return n;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--){
int res = sol();
ex(res);
}
return 0;
}
CF959D Mahmoud and Ehab and another array construction task
题目描述
Mahmoud 有一个包含 \(n\) 个整数的数组 \(a\)。他让 Ehab 找出另一个长度相同的数组 \(b\),使得:
- \(b\) 字典序大于等于 \(a\)。
- \(b_i \geq 2\)。
- \(b\) 是两两互质的:对于每个 \(1 \leq i < j \leq n\),\(b_i\) 和 \(b_j\) 互质,即 \(GCD(b_i, b_j) = 1\),其中 \(GCD(w, z)\) 表示 \(w\) 和 \(z\) 的最大公约数。
Ehab 想选择一个特殊的数组,所以他希望在所有满足条件的数组中,字典序最小的那个。你能帮他找到吗?
如果存在某个下标 \(i\),使得 \(x_i > y_i\),且对于所有 \(1 \leq j < i\),\(x_j = y_j\),则数组 \(x\) 字典序大于数组 \(y\)。如果对于所有 \(1 \leq i \leq n\),\(x_i = y_i\),则数组 \(x\) 等于数组 \(y\)。
题解
构造过程可以贪心地进行:逐位确定 \(b_i\),并维护一个“可用数字”集合,保证后续选的数字两两互质。
具体来说,我们维护一个集合 \(S\),初始包含所有整数。同时用一个标记表示当前是否已经“严格大于”原数组。从前往后处理每一位:
- 若之前位全部等于 \(a\),则从 \(S\) 中选第一个 \(\geq a_i\) 的数作为 \(b_i\)。
- 若之前存在位置大于 \(a\),则直接选 \(S\) 中最小的数作为 \(b_i\)。
选完 \(b_i\) 后,如果 \(b_i > a_i\),就将 \(f\) 置为 \(1\)。同时,为了保证最终数组两两互质,\(b_i\) 的任意质因子都不能在后续的数中再次出现。因此我们找出 \(b_i\) 的所有质因子,把它们的倍数全部从 \(S\) 中删除。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2e6 + 5;
int a[M], ans[M];
set<int>s;
void dl(int x){
for(int i = 2; i * i <= x; i++){
if(x % i == 0){
for(int j = i; j < M - 2; j += i){
if(s.count(j)) s.erase(j);
}
while(x % i == 0) x /= i;
}
}
if(x > 1){
for(int j = x; j < M - 2; j += x){
if(s.count(j)) s.erase(j);
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
for(int i = 2; i < M - 2; i++){
s.insert(i);
}
bool f = 0;
for(int i = 1; i <= n; i++){
auto it = f ? s.begin() : s.lower_bound(a[i]);
if(*it > a[i]) f = 1;
ans[i] = *it;
dl(*it);
}
for(int i = 1; i <= n; i++){
cout << ans[i] << ' ';
}
return 0;
}
CF708B Recover the String
题目描述
对于每个由字符 0 和 1 组成的字符串 \(s\),可以定义四个整数 \(a_{00}\)、\(a_{01}\)、\(a_{10}\) 和 \(a_{11}\),其中 \(a_{xy}\) 表示字符串 \(s\) 中等于序列 \(\{x, y\}\) 的长度为 \(2\) 的子序列的数量。
在本题中,给定四个整数 \(a_{00}\)、\(a_{01}\)、\(a_{10}\)、\(a_{11}\),你需要找出任意一个非空字符串 \(s\),使得它对应的这四个值与给定值相同,或者判断不存在这样的字符串。可以证明,如果存在至少一个解,则一定存在长度不超过 \(1000000\) 的解。
题解
我们注意到 \(s\) 中有 \(n\) 个 \(0\) 和 \(m\) 个 \(1\) 时,\(a_{00} = \frac{n \times(n + 1)}{2}, ~a_{11} = \frac{m \times(m + 1)}{2}\),所以如果 \(a_{00}\) 和 \(a_{11}\) 无法表达为这种形式直接输出无解。
还有,不管是 \(01\) 还是 \(10\),都是一个 \(0\) 和一个 \(1\) 在一起,所以 \(a_{01} + a_{10} = n \times m\),如果不满足也是无解。
然后我们考虑 \(0 \cdots01 \cdots1\) 这样的字符串,显然全都是 \(01\),然后我们交换一个 \(0\) 和 \(1\) 的位置,就会多 \(10\) 的数量,所以如果可以往后移就往后移,直到移到最后或满足答案了就可以了。
注意特判全都是 \(0\) 的情况,是有答案的,构造就是一个字符 \(0\)。还有要特判答案只有一种字符的情况。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int iv(int x) {
int l = 1, r = 1000000;
while (r - l > 1) {
int m = (l + r) / 2;
if (m * (m - 1) / 2 > x) r = m;
else l = m;
}
if (l * (l - 1) / 2 != x) {
cout << "Impossible";
exit(0);
}
return l;
}
void sol(){
int a, b, c, d;
cin >> a >> b >> c >> d;
if (a + b + c + d == 0) {
cout << "0\n";
return;
}
int c0 = iv(a), c1 = iv(d);
if (a == 0 || d == 0) {
if (b + c == 0) {
if (c0 == 1) c0 = 0;
if (c1 == 1) c1 = 0;
cout << string(c0, '0') << string(c1, '1');
return;
}
}
if(c0 * c1 != b + c){
cout << "Impossible";
return;
}
string s(c0 + c1, '0');
for (int i = 0; i < s.size(); ++i) {
if (c0 == 0 || b < c1) {
s[i] = '1';
c -= c0;
--c1;
} else {
b -= c1;
--c0;
}
}
cout << s;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
while(T--) sol();
return 0;
}
CF1949C Annual Ants' Gathering
题面描述
给定一棵 \(n\) 个点的树,每个点的初始点权为 \(1\)。你可以将一个点 \(u\) 的点权转到点 \(v\) 当且仅当 \(u\) 的点权小于等于 \(v\) 的点权。问是否可以通过若干次操作使一个点的点权为 \(n\),即将所有点的点权集中在一个点上。
题解
考虑合并过程,设存在一种操作序列,将所有蚂蚁聚集到点 \(R\),以 \(R\) 为根将树视为有根树,设 \(R\) 的子节点对应的子树大小分别为 \(s_1,s_2,\cdots,s_k\)。
若某子树大小 \(> \frac{n}{2}\),则它永远无法被合并到 \(R\),因为直到最后 \(R\) 的蚂蚁数最多为 \(n−s_i\),永远小于 \(s_i\)。因此所有子树大小必须 \(\le \frac n 2\),而这正是重心定义。因此只需检查重心即可。
然后我们就把每个节点的子树大小排序,依次合并即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 200005;
int n;
vector<int> vec[M];
int siz[M], siz2[M];
vector<int> v2;
void dfs1(int u, int fa) {
siz[u] = 1;
int maxn = 0;
for (int v : vec[u]) {
if (v == fa) continue;
dfs1(v, u);
siz[u] += siz[v];
maxn = max(maxn, siz[v]);
}
maxn = max(maxn, n - siz[u]);
if (maxn <= n / 2) {
v2.push_back(u);
}
}
void dfs2(int u, int fa) {
siz2[u] = 1;
for (int v : vec[u]) {
if (v == fa) continue;
dfs2(v, u);
siz2[u] += siz2[v];
}
}
bool check(int u, int fa) {
vector<int> s;
for (int v : vec[u]) {
if (v == fa) continue;
if (!check(v, u)) return false;
s.push_back(siz2[v]);
}
if (s.empty()) return true;
sort(s.begin(), s.end());
int cur = 1;
for (int x : s) {
if (x > cur) return false;
cur += x;
}
return true;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
if (n == 1) {
cout << "YES\n";
return 0;
}
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs1(1, 0);
for (int rt : v2) {
dfs2(rt, 0);
if (check(rt, 0)) {
cout << "YES\n";
return 0;
}
}
cout << "NO\n";
return 0;
}
CF1245D Shichikuji and Power Grid
题目描述
在 X 县有 \(n\) 个新城市。城市编号从 \(1\) 到 \(n\)。第 \(i\) 个城市位于距离神社北方 \(x_i\) 千米、东面 \(y_i\) 千米的位置。可能存在 \(i \ne j\) 但 \((x_i, y_i) = (x_j, y_j)\) 的情况。
Shichikuji 必须为每个城市供电,可以通过在该城市建造发电站,或者将该城市与已经有电的城市连接来实现。也就是说,如果一个城市自身有发电站,或者通过直接连接或一系列连接与有电的城市相连,则该城市就有电。
- 在第 \(i\) 个城市建造发电站的费用为 \(c_i\) 日元;
- 将第 \(i\) 个城市与第 \(j\) 个城市连接的费用为每千米 \((k_i + k_j)\) 日元。电线只能沿着正北、正南、正东、正西方向铺设,电线可以相互交叉。每根电线的两个端点都必须在某个城市。如果第 \(i\) 个城市与第 \(j\) 个城市连接,则电线会沿任意一条最短路径铺设。因此,连接第 \(i\) 个城市与第 \(j\) 个城市所需电线的长度为 \(|x_i - x_j| + |y_i - y_j|\) 千米。
Shichikuji 希望以尽可能少的钱完成这项工作。你需要为 Shichikuji 提供以下信息:为所有城市供电所需的最小日元数、在哪些城市建造发电站,以及需要建立哪些连接。如果存在多种选择城市和连接方式使得总花费最小,则输出任意一种即可。
题解
我们发现建设发电站这个 \(c_i\) 点权比较难弄,因此我们将它转换为边权。
我们不妨假设一个超级源点 \(n + 1\),让它向所有点连边,边权为 \(c_i\),这样选择在 \(i\) 建设发电站就相当于是选择了 \(n + 1 \leftrightarrow i\) 的双向边。然后再连上本来有的边,这个题就变得很简单了,在这个新图上跑一边最小生成树即可。
注意这个图是稠密图,边的数量是 \(\mathcal{O}(n^2)\) 的,考虑使用 prim 算法。
Code
#include<bits/stdc++.h>
#define int long long
const int M = 2005;
const int inf = 1e16;
using namespace std;
struct edge {
int v, d;
};
vector<edge>vec[M];
int x[M], y[M], k[M];
int d[M], vis[M], fa[M];
int dis(int x, int y, int x2, int y2) {
return abs(x - x2) + abs(y - y2);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
}
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
vec[i].push_back((edge) {n + 1, x});
vec[n + 1].push_back((edge) {i, x});
}
for (int i = 1; i <= n; i++) {
cin >> k[i];
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int val = dis(x[i], y[i], x[j], y[j]) * (k[i] + k[j]);
vec[i].push_back((edge) {j, val});
vec[j].push_back((edge) {i, val});
}
}
fill(d + 1, d + n + 1, inf);
d[n + 1] = 0;
int tot = 0;
for (int i = 1; i <= n + 1; i++) {
int u = -1, mx = inf;
for (int j = 1; j <= n + 1; j++) {
if (!vis[j] && d[j] < mx) {
mx = d[j];
u = j;
}
}
vis[u] = 1;
tot += d[u];
for (auto &[v, dst] : vec[u]) {
if (dst < d[v] && !vis[v]) {
d[v] = dst, fa[v] = u;
}
}
}
vector<int>ans1;
vector<pair<int, int>>link;
for (int i = 1; i <= n; i++) {
if (fa[i] == n + 1) {
ans1.push_back(i);
} else if (fa[i] != -1) {
int u = i, v = fa[i];
if (u > v) swap(u, v);
link.push_back(make_pair(u, v));
}
}
cout << tot << '\n' << ans1.size() << '\n';
for (auto e : ans1) cout << e << ' ';
cout << '\n' << link.size() << '\n';
for (auto &[u, v] : link) cout << u << ' ' << v << '\n';
return 0;
}
CF981D Bookshelves
题目描述
把长为 \(n\) 的一个序列分为 \(k\) 段,每段长度 \(\geq 1\),求每段和的按位与最大。
题解
我们有一个朴素的想法,设 \(dp_{i,j}\) 表示前 \(i\) 位分成 \(j\) 段的答案,但这显然不行,因为按位与并不满足最优子结构,有可能存在后面某一位没有高位的 \(1\) 导致无法取最大值的情况。
注意到是按位与,所以肯定是高位比低位更优。我们考虑答案的每一位,显然高位能填 \(1\) 就去尽量满足。所以我们设目前答案为 \(res\),然后设 \(dp_{i, j}\) 表示前 \(i\) 位分成 \(j\) 段,能否使得答案保持 \(res\)。然后每次枚举位的时候,转移就很显然了:dp[i][j] |= ( dp[k][j-1] & ((s[i] - s[k]) & x) == x )。其中 \(s_i\) 为前缀和。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 78;
int a[M], dp[M][M], s[M];
int n, k, res;
int ck(int x){
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= k; j++){
for(int k = 0; k < i; k++){
int nw = (s[i] - s[k]) & x;
dp[i][j] |= dp[k][j - 1] & (nw == x);
}
}
}
return dp[n][k];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> k;
for(int i = 1; i <= n; i++) {
int x;
cin >> x;
s[i] = s[i - 1] + x;
}
for(int i = 60; i >= 0; i--){
int x = 1ll << i;
if(ck(res | x)) res |= x;
}
cout << res;
return 0;
}
CF811C Vladik and Memorable Trip
题目描述
Vladik 目前在起始火车站,现在共有 \(n\) 个人(包括 Vladik)想要上火车。他们已经按照一定的顺序排好队,对于每个人都知道其目的地城市的编码 \(a_{i}\)。
列车长会从这 \(n\) 个人的队列中选出若干个不相交的连续区间(不要求覆盖全部人)。在同一个区间中的人会被分配到同一个车厢。选取区间时,有一个要求:如果有至少一个人前往城市 \(x\),那么所有前往城市 \(x\) 的人必须都在同一个车厢中,即他们不能分在不同的区间。注意,所有前往城市 \(x\) 的人要么一起去且在同一个车厢,要么都不去(即没有被分配到任何车厢)。
对于区间 \([l, r]\),其“舒适度”定义为该区间中所有不同城市编码的异或和(XOR),即将区间中所有不重复的 \(a_i\) 按位异或。
本次火车旅行的总舒适度为所有已选区间舒适度之和。现在,请你帮 Vladik 计算最大可能的总舒适度。
题解
我们发现这个 dp 是容易设计的, \(dp_i = \max\limits_{j=1}^i dp_{j - 1} + \oplus_ {k = j}^i a_k\)。但是本题的难点在于如何判断转移的区间是否合法。
既然题目说的是所有前往城市 \(x\) 的人,那么我们可以预处理出第一个和最后一个 \(x\) 的位置,每次转移的时候记录一下区间 \([j, i]\) 内的数字的最左最右边界,即 \([\min(pre_{a_i}), \max(suf_{a_i})]\),然后判断这个区间是否被 \([j, i]\) 包含即可判断是否可以转移。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 5005;
int a[M], fst[M], lst[M];
int dp[M], vis[M];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
memset(fst, 0x3f, sizeof(fst));
for(int i = 1; i <= n; i++){
cin >> a[i];
if(fst[a[i]] > n) fst[a[i]] = i;
lst[a[i]] = i;
}
dp[0] = 0;
for(int i = 1; i <= n; i++){
dp[i] = dp[i - 1];
int sum = 0, mx = 0, mn = i + 1;
vector<int>vec;
for(int j = i; j >= 1; j--){
int x = a[j];
if(!vis[x]) {
vis[x] = 1;
sum ^= x;
vec.push_back(x);
mn = min(mn, fst[x]);
mx = max(mx, lst[x]);
}
if(mn >= j && mx <= i) dp[i] = max(dp[i], dp[j - 1] + sum);
}
for(auto x : vec) vis[x] = 0;
}
cout << dp[n];
return 0;
}
CF552D Vanya and Triangles
题目描述
给定平面上 \(n\) 个不同的点,求能构成面积不为零的三角形的数量。
题解
正难则反,考虑面积为零的三角形,肯定是三点共线,于是问题转化为了求三点共线的数量。
我们枚举两个点,然后计算这两点的斜率存进 map 里面。那么一个出现 \(cnt\) 次的斜率对答案的贡献为 \(\frac{cnt(cnt - 1)}{2}\)。然后三点共线的话有 \(k_{AB} = k_{AC} = k_{BC}\),易知按这样算的话每个三角形被重复算了 \(3\) 次,在答案上除以 \(3\) 即可。总数也是显然的,为 \(\frac{n(n-1)(n-2)}{6}\)。
注意处理斜率互为相反数,它们是共线的。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2005;
int x[M], y[M];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
}
int tot = n * (n - 1) * (n - 2) / 6;
int sum = 0;
for (int i = 1; i <= n; i++) {
map<pair<int, int>, int> cnt;
for (int j = 1; j <= n; j++) {
if (i == j) continue;
int dx = x[i] - x[j];
int dy = y[i] - y[j];
int g = __gcd(dx, dy);
dx /= g;
dy /= g;
if (dx < 0 || (dx == 0 && dy < 0)) {
dx = -dx;
dy = -dy;
}
cnt[{dx, dy}]++;
}
for (auto &[d, c] : cnt) {
sum += c * (c - 1) / 2;
}
}
cout << tot - sum / 3;
return 0;
}
CF965D Single-use Stones
题目描述
有许多青蛙想要过河。一条河的宽度为 \(w\) 单位,但青蛙一次最多只能跳 \(l\) 单位远,其中 \(l < w\)。青蛙也可以跳比 \(l\) 短的距离,但不能跳得更远。幸运的是,河中有一些石头可以帮助它们。
这些石头位于距离青蛙当前所在河岸 \(i\) 单位的整数位置上。在距离 \(i\) 单位处有 \(a_i\) 块石头。每块石头只能被一只青蛙使用一次,之后就会沉入水中。问最多有多少只青蛙能够成功过河。
题解
我们发现 \(\forall i \in [1, w - l]\),如果区间 \([i, i + l]\) 最多能够使得 \(cnt\) 只青蛙跳过,那么答案就不可能大于 \(cnt\),证明显然。
然后我们发现这个 \(cnt\) 其实就相当于 \(\sum\limits_{j=i}^{i + l} a_j\),做完了。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1e5 + 5;
int a[M], w, len;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> w >> len;
for(int i = 1; i < w; i++) cin >> a[i], a[i] += a[i - 1];
int ans = 1e16;
for(int i = len; i < w; i++) {
ans = min(ans, a[i] - a[i - len]);
}
cout << ans;
return 0;
}
CF1421D Hexagons
题目描述
如下图所示的平面六边形镶嵌。

Nicks 希望从标记为 \((0, 0)\) 的单元格出发,前往给定坐标的某个单元格。她可以从一个六边形移动到任意一个相邻的六边形,但每个方向都有一个对应的花费。花费仅取决于你前进的方向。例如,从 \((0, 0)\) 到 \((1, 1)\) 的花费与从 \((-2, -1)\) 到 \((-1, 0)\) 的花费完全相同。输入中按照下图所示的顺序给出了 \(c_1, c_2, c_3, c_4, c_5, c_6\) 六个方向的花费。

请输出从原点 \((0, 0)\) 到给定单元格的最小路径花费。
题解
拿到题我们发现六边形非常难做,于是我们把六个方向画出来,大概是下面的图(windows 鼠标手画的,轻喷喵):

于是我们可以发现 \(c_1\) 相当于 \(c_2\) 和 \(c_4\),\(c_3\) 相当于是 \(c_4\) 和 \(c_2\),等等。所以我们在这个图上跑一边最短路,求出向每一个方向走一步的最小花费。代码里使用的是 Bellman-Ford,循环 \(15\) 次是因为松弛操作最多进行的轮数等于边数。
然后就可以开始大力分类讨论。
- \(x, y \ge 0\),\(c_1\) 与 (\(c_2\) 或 \(c_6\)) 组合。
- \(x, y \le 0\),\(c_4\) 与 (\(c_3\) 或 \(c_5\)) 组合。
- \(x \ge 0, y \le 0\),\(c_6\) 与 \(c_5\) 组合。
- \(x \le 0, y \ge 0\),\(c_3\) 与 \(c_2\) 组合。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
void sol() {
int x, y, c1, c2, c3, c4, c5, c6;
cin >> x >> y >> c1 >> c2 >> c3 >> c4 >> c5 >> c6;
for(int i = 1; i <= 15; i++) {
c1 = min(c1, c6 + c2);
c2 = min(c2, c1 + c3);
c3 = min(c3, c2 + c4);
c4 = min(c4, c3 + c5);
c5 = min(c5, c4 + c6);
c6 = min(c6, c5 + c1);
}
if(x >= 0 && y >= 0){
if(x > y) cout << y * c1 + (x - y) * c6 << '\n';
else cout << x * c1 + (y - x) * c2 << '\n';
}
else if(x <= 0 && y <= 0){
if(x < y) cout << -y * c4 - (x - y) * c3 << '\n';
else cout << -x * c4 - (y - x) * c5 << '\n';
}
else if(x >= 0 && y <= 0) cout << x * c6 - y * c5 << '\n';
else if(x <= 0 && y >= 0) cout << -x * c3 + y * c2 << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) sol();
return 0;
}
CF961E Tufurama
题目描述
电视剧共有 \(n\) 季(编号为 \(1\) 到 \(n\)),第 \(i\) 季有 \(a_i\) 集(编号为 \(1\) 到 \(a_i\))。Polycarp 认为,如果存在一对整数 \(x\) 和 \(y\)(\(x<y\)),使得既存在第 \(x\) 季第 \(y\) 集,又存在第 \(y\) 季第 \(x\) 集,那么其中一个搜索结果就会出现错误。请你帮助 Polycarp 计算这样的 \((x, y)\) 对的数量!
题解
将题目转化为求解满足 \(x \le \min(a_y, y),y \le a_x\) 的 \((x,y)\) 对数。
按 \(x\) 从小到大扫描。对于当前的 \(x\),我们关心所有 \(y > x\) 且 \(a_y \ge x\) 的位置。如果能在树状数组中动态维护所有满足 \(a_i \ge\) 当前 \(x\) 的位置(即这些位置“有效”),那么对于每个 \(x\),只需在树状数组中查询区间 \((x,\min(a_x,n)]\) 内有效位置的个数即可。
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int M = 2e5 + 5;
int a[M], c[M], n;
vector<pair<int, int>> vec;
int lowbit(int x) {return x & (-x);}
void upd(int x, int y){
for(; x <= n; x += lowbit(x)) c[x] += y;
}
int qry(int x){
int res = 0;
for(; x; x -= lowbit(x)) res += c[x];
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
a[i] = min(a[i], n + 1);
vec.push_back(make_pair(a[i], i));
upd(i, 1);
}
sort(vec.begin(), vec.end());
int p = 0, ans = 0;
for(int x = 1; x <= n; x++){
while(p < n && vec[p].first < x){
upd(vec[p].second, -1);
p++;
}
if(a[x] > x){
int r = min(a[x], n);
ans += qry(r) - qry(x);
}
}
cout << ans;
return 0;
}
CF1092F Tree with Maximum Cost
题目描述
给定一棵恰好有 \(n\) 个顶点的树。树是一个连通无向图,包含 \(n-1\) 条边。树上的每个顶点 \(v\) 都被赋予了一个值 \(a_v\)。
定义 \(dist(x, y)\) 为顶点 \(x\) 和 \(y\) 之间的距离,即它们之间简单路径上的边数。
我们将树的代价定义如下:首先,固定树上的某个顶点 \(v\)。然后,树的代价为 \(\sum\limits_{i = 1}^{n} dist(i, v) \cdot a_i\)。
你的任务是计算,如果可以任意选择 \(v\),树的最大可能代价是多少。
题解
一眼换根 dp。
首先我们第一遍 dfs,计算出以 \(1\) 为根的答案 \(dp_1 = \sum\limits_{i = 1}^n dep_i \times a_i\)。
然后我们考虑换根对答案造成的影响。
假设当前节点为 \(u\),我们求出了 \(dp_u\),现在要换到 \(u\) 的儿子 \(v\) 上面。那么 \(v\) 以及它的子树距离都会减少 \(1\),然后其他的点距离增加 \(1\)。所以 \(dp_v = dp_u + \sum\limits_{i=1}^n a_i - 2 \times \sum\limits_{v = son_u} a_v\)。再跑一次 dfs 即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2e5 + 5;
int a[M], dep[M], sz[M], f[M], sum;
vector<int>vec[M];
void dfs1(int u, int fa){
dep[u] = dep[fa] + 1;
sz[u] = a[u];
for(auto v : vec[u]){
if(v == fa) continue;
dfs1(v, u);
sz[u] += sz[v];
}
}
void dfs2(int u, int fa){
if(u != 1) f[u] = f[fa] + sum - 2 * sz[u];
for(auto v : vec[u]){
if(v == fa) continue;
dfs2(v, u);
}
}
void sol(){
int n;
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i], sum += a[i];
for(int i = 1, u, v; i < n; i++){
cin >> u >> v;
vec[u].push_back(v);
vec[v].push_back(u);
}
dep[0] = -1;
dfs1(1, 0);
for(int i = 1; i <= n; i++) f[1] += dep[i] * a[i];
dfs2(1, 0);
cout << *max_element(f + 1, f + 1 + n);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
while(T--) sol();
return 0;
}
CF1163C Power Transmission
题目描述
在发电站入口处,有一张描述复杂布线系统的地图。每根电线杆是平面上的一个点,坐标为 $ (x_i, y_i) $。由于每根电线杆都不同,所有代表这些电线杆的点都是不同的。此外,每两根电线杆之间都由一根电线相连。电线在平面上是一条无限延伸的直线。如果有多于两根电线杆共线,它们只通过一根公共电线相连。请问有多少对电线是相交的?
题解
不是为什么 Easy & Hard version 一个难度喵?
Easy ver. 直接跑暴力就可以了。
对于 Hard ver. 的话,显然我们发现斜率不同的两条线绝对会相交。所以直接统计有多少种斜率,每种斜率不同的截距有几个,注意三点一线的情况就可以了。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1005;
int x[M], y[M];
void sol() {
map<pair<pair<int,int>, int>, int> line;
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int A = y[i] - y[j];
int B = x[j] - x[i];
int C = x[i] * y[j] - x[j] * y[i];
int g = __gcd(abs(A), __gcd(abs(B), abs(C)));
A /= g; B /= g; C /= g;
if (A < 0 || (A == 0 && B < 0)) {
A = -A; B = -B; C = -C;
}
line[{{A, B}, C}]++;
}
}
vector<pair<pair<int,int>, int>> b;
for (auto &p : line) {
b.push_back(p.first);
}
int m = b.size();
map<pair<int,int>, int> d;
for (auto &l : b) {
int A = l.first.first, B = l.first.second;
int g = __gcd(abs(A), abs(B));
A /= g; B /= g;
if (A < 0 || (A == 0 && B < 0)) {
A = -A; B = -B;
}
d[{A, B}]++;
}
int ans = m * (m - 1) / 2;
for (auto &p : d) {
int c = p.second;
ans -= c * (c - 1) / 2;
}
cout << ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
while (T--) sol();
return 0;
}
CF1237C2 Balanced Removals (Harder)
题目描述
在三维空间中有 \(n\) 个互不相同的点,编号从 \(1\) 到 \(n\)。第 \(i\) 个点的坐标为 \((x_i, y_i, z_i)\)。点的数量 \(n\) 是偶数。
你需要通过一系列 \(\frac{n}{2}\) 次“消除”操作移除所有 \(n\) 个点。每次操作,你可以移除任意两个尚未被移除且构成“完全平衡对”的点 \(a\) 和 \(b\)。如果不存在其他尚未被移除的点 \(c\) 位于点 \(a\) 和 \(b\) 的轴对齐最小包围盒内,则点对 \(a\) 和 \(b\) 被称为“完全平衡对”。
形式化地说,有且仅有 \(\min(x_a, x_b) \le x_c \le \max(x_a, x_b)\),\(\min(y_a, y_b) \le y_c \le \max(y_a, y_b)\),且 \(\min(z_a, z_b) \le z_c \le \max(z_a, z_b)\),则点 \(c\) 位于点 \(a\) 和 \(b\) 的轴对齐最小包围盒内。注意,包围盒可能是退化的。
请找出一种方案,用 \(\frac{n}{2}\) 次操作移除所有点。
题解
先考虑一维怎么做?我们可以对其按照坐标进行排序,两两配对顺序输出,可能会多余一个。
那拓展到二维呢?对于每个 \(y\),找出所有 \(y\) 坐标相同的点,进行与一维相似的操作。操作完成后,每个 \(y\) 上最多有一个点。最后将剩下的 \(y\) 进行排序,进行一次一维操作即可。
那三维呢?慢慢降维就可以了。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int dfs(vector<int>& d, int k, const vector<vector<long long>>& pts) {
if (k == 3) {
return d[0];
}
map<long long, vector<int>> g;
for (int idx : d) {
g[pts[idx][k]].push_back(idx);
}
vector<int> rem;
for (auto& v : g) {
int ret = dfs(v.second, k + 1, pts);
if (ret != -1) rem.push_back(ret);
}
for (int i = 0; i + 1 < rem.size(); i += 2) {
cout << rem[i] + 1 << " " << rem[i + 1] + 1 << "\n";
}
return (rem.size() % 2 == 1) ? rem.back() : -1;
}
void solve() {
int n;
cin >> n;
vector<vector<long long>> pts(n, vector<long long>(3));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < 3; ++j) {
cin >> pts[i][j];
}
}
vector<int> d(n);
iota(d.begin(), d.end(), 0);
dfs(d, 0, pts);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
while (T--) solve();
return 0;
}
CF1285D Dr. Evil Underscores
题目描述
有 \(n\) 个整数 \(a_1, a_2, \dots, a_n\),选择一个整数 \(X\),使得 \(\underset{1 \leq i \leq n}{\max} (a_i \oplus X)\) 的值尽可能小,其中 \(\oplus\) 表示按位异或。
题解
对于异或,我们想到用 01-trie 去维护。
我们首先把数字插入 Trie 中,然后对于每个节点,如果只有一个儿子那么代价为 \(0\),如果有两个儿子,那么取代价最小的。dfs 即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int trie[N << 5][2], tot = 0;
int a[N];
void insert(int n) {
int p = 0;
for (int i = 30; i >= 0; i--) {
int t = (n >> i) & 1;
if (!trie[p][t]) trie[p][t] = ++tot;
p = trie[p][t];
}
}
int dfs(int p, int bit) {
if (bit < 0) return 0;
if (!trie[p][0] && trie[p][1]) return dfs(trie[p][1], bit - 1);
if (trie[p][0] && !trie[p][1]) return dfs(trie[p][0], bit - 1);
return (1 << bit) + min(dfs(trie[p][0], bit - 1), dfs(trie[p][1], bit - 1));
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i], insert(a[i]);
}
cout << dfs(0, 30);
return 0;
}
CF1704D Magical Array
题目描述
Eric 有一个长度为 \(m\) 的数组 \(b\),然后他通过以下方式,从数组 \(b\) 生成了 \(n\) 个额外的数组 \(c_1, c_2, \dots, c_n\),每个数组的长度均为 \(m\):
最开始,对于每个 \(1 \le i \le n\),都有 \(c_i = b\)。Eric 秘密选择了一个整数 \(k\)(\(1 \le k \le n\)),并将 \(c_k\) 选为特殊数组。
Eric 可以对数组 \(c_t\) 执行两种操作:
- 操作 1:选择两个整数 \(i\) 和 \(j\)(\(2 \leq i < j \leq m-1\)),将 \(c_t[i]\) 和 \(c_t[j]\) 各减 \(1\),同时将 \(c_t[i-1]\) 和 \(c_t[j+1]\) 各加 \(1\)。该操作只能用于非特殊数组,即 \(t \neq k\)。
- 操作 2:选择两个整数 \(i\) 和 \(j\)(\(2 \leq i < j \leq m-2\)),将 \(c_t[i]\) 和 \(c_t[j]\) 各减 \(1\),同时将 \(c_t[i-1]\) 和 \(c_t[j+2]\) 各加 \(1\)。该操作只能用于特殊数组,即 \(t = k\)。注意,如果执行操作后数组中有任何元素变为小于 \(0\),则不能执行该操作。
接下来,Eric 进行了如下操作:
- 对于每个非特殊数组 \(c_i\)(\(i \neq k\)),Eric 仅对其执行操作 1,且至少执行一次。
- 对于特殊数组 \(c_k\),Eric 仅对其执行操作 2,且至少执行一次。
最后,Eric 丢弃了数组 \(b\)。
现在给定数组 \(c_1, c_2, \dots, c_n\),你的任务是找出特殊数组的编号 \(k\),并且需要求出在该数组上执行操作 2 的次数。
题解
神经题目,题面啰嗦,不过还是有理解的。
操作二是把 \(j + 2\) 位置 \(+1\),而操作一是 \(j+1\) 位置,这启发我们去计算 \(\sum\limits_{i=1}^n a_i \times i\),每进行一次操作二这个数值就会 \(+1\)。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
void sol() {
int n, m;
cin >> n >> m;
int mx = 0, mn = 1e18, idx = -1;
for (int i = 1; i <= n; ++i) {
int sum = 0;
for (int j = 1; j <= m; ++j) {
int x; cin >> x;
sum += j * x;
}
if (sum > mx) {
mx = sum;
idx = i;
}
if (sum < mn) {
mn = sum;
}
}
cout << idx << ' ' << mx - mn << '\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while(T--) sol();
return 0;
}
CF1922D Berserk Monsters
题目描述
Monocarp 正在玩一款电脑游戏(又一次)。你猜他在做什么?没错,就是在杀怪物。
有 \(n\) 个怪物排成一排,从 \(1\) 到 \(n\) 编号。第 \(i\) 个怪物有两个参数:攻击值 \(a_i\) 和防御值 \(d_i\)。为了杀死这些怪物,Monocarp 对它们施加了狂暴法术,让它们互相攻击,而不是攻击 Monocarp 的角色。
战斗共进行 \(n\) 轮。每一轮,发生如下事件:
- 首先,每个存活的怪物 \(i\) 会对其左边最近的存活怪物(如果存在)和右边最近的存活怪物(如果存在)各造成 \(a_i\) 点伤害;
- 然后,每个在本轮受到的总伤害超过其防御值 \(d_j\) 的存活怪物 \(j\) 会死亡。也就是说,第 \(j\) 个怪物当且仅当其防御值 \(d_j\) 严格小于本轮受到的总伤害时会死亡。
请你计算每一轮中死亡的怪物数量。
题解
双向链表模拟题,注意是同时造成伤害,每轮统一结算。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 3e5 + 5;
int a[M], d[M], pre[M], suf[M];
bool vis[M], s[M];
void sol() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> d[i];
a[0] = a[n + 1] = 0;
for (int i = 1; i <= n; i++) {
pre[i] = i - 1;
suf[i] = i + 1;
vis[i] = false;
s[i] = false;
}
pre[1] = 0;
suf[n] = n + 1;
vector<int> cur;
for (int i = 1; i <= n; i++) cur.push_back(i);
for (int T = 1; T <= n; T++) {
vector<int> gg;
for (int v : cur) {
if (vis[v]) continue;
if (a[pre[v]] + a[suf[v]] > d[v])
gg.push_back(v);
}
cout << gg.size() << ' ';
for (int v : gg) vis[v] = true;
vector<int> nxt;
for (int v : gg) {
int l = pre[v], r = suf[v];
if (l >= 1) suf[l] = r;
if (r <= n) pre[r] = l;
if (l >= 1 && !vis[l] && !s[l]) {
nxt.push_back(l);
s[l] = true;
}
if (r <= n && !vis[r] && !s[r]) {
nxt.push_back(r);
s[r] = true;
}
}
for (int x : nxt) s[x] = false;
cur = nxt;
if (cur.empty()) {
for (int j = T + 1; j <= n; j++) cout << 0 << ' ';
break;
}
}
cout << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) sol();
return 0;
}
CF1012B Chemical table
题目描述
伊诺波利斯大学的科学家们正在继续研究元素周期表。已知有 \(n·m\) 种元素,它们组成了一个周期表:一个有 \(n\) 行 \(m\) 列的矩形。每个元素可以用其在表中的坐标 \((r,c)\)(\(1 \leq r \leq n\),\(1 \leq c \leq m\))来描述。
最近,科学家们发现,对于周期表中任意四个不同的元素,如果它们恰好形成一个边平行于表格边界的矩形,并且他们已经拥有其中三个元素的样本,那么就可以通过核聚变制造出第四个元素的样本。也就是说,如果我们拥有 \((r_{1},c_{1})\)、\((r_{1},c_{2})\)、\((r_{2},c_{1})\) 这三个位置的元素(其中 \(r_{1} \neq r_{2}\) 且 \(c_{1} \neq c_{2}\)),那么就可以制造出 \((r_{2},c_{2})\) 位置的元素。

用于核聚变的样本不会被消耗,可以在后续的核聚变中继续使用。新制造出来的元素也可以用于后续的核聚变。
科学家们已经拥有了 \(q\) 个元素的样本。他们希望最终获得所有 \(n·m\) 个元素的样本。为此,他们可以从其他实验室购买一些样本,然后通过任意次数、任意顺序的核聚变制造出剩余的元素。请帮助他们计算,最少需要购买多少个元素的样本。
题解
神题。
首先我们想到的是二分图匹配,把行和列连边,看需要加多少边。然后我们注意到这个新的边不影响原二分图的连通块个数,于是直接并查集维护。不是这谁想得到
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 4e5 + 5;
int fa[M];
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m, q, ans = 0;
cin >> n >> m >> q;
for (int i = 1; i <= n + m; i++) fa[i] = i;
for (int i = 1, x, y; i <= q; i++) {
cin >> x >> y;
x = find(x), y = find(y + n);
fa[x] = y;
}
for (int i = 1; i <= n + m; i++) if (fa[i] == i) ans++;
cout << ans - 1;
return 0;
}
CF1725C Circular Mirror
题目描述
Pak Chanek 有一面圆形的镜子。镜子的圆周上有 \(N\) 盏灯,顺时针编号为 \(1\) 到 \(N\)。从第 \(i\) 盏灯到第 \(i+1\) 盏灯的弧长为 \(D_i\),其中 \(1 \leq i \leq N-1\)。而第 \(N\) 盏灯到第 \(1\) 盏灯之间的弧长为 \(D_N\)。
Pak Chanek 想用 \(M\) 种不同的颜色给这些灯染色。每盏灯可以被染成 \(M\) 种颜色中的一种。但是,不允许存在三盏不同的灯,使得这三盏灯的颜色相同,并且以这三盏灯为顶点所构成的三角形是直角三角形(即其中一个角恰好为 \(90\) 度)。
题解
根据圆周角定理的推论,如果两个点恰好位于一条直径的两端,那么随便取第三个点,都能构成直角三角形。所以我们直接统计有多少对点能够构成直径,假设有 \(d\) 对,共 \(2d\) 盏灯。剩下 \(N-2d\) 盏灯没有对径点,称为普通灯。
我们将 \(d\) 对点分成两类处理,即两盏灯是否染同一种颜色。
设我们选择 \(i\) 对染同一种颜色它们需要满足:
- 这 \(i\) 个完整对必须使用互不相同的颜色。
- 这 \(i\) 种颜色不能再用于任何其他灯。
剩下的 \(d-i\) 个拆开对:
- 每对中的两盏灯必须异色。
- 不能使用已被完整对占用的 \(i\) 种颜色。
- 对于每个拆开对,我们固定其中一盏为“自由灯”(可选 \(M - i\) 种颜色),另一盏为“受限灯”(必须与同对的自由灯不同色,可选 \(M-i-1\) 种颜色)。
普通灯与拆开的对相同。所以方案数 \(ans_i = \binom{d}{i} \times P(M, i) \times (M-i)^{N-d-i} \times (M-i-1)^{d-i}\)
其中 \(\binom{d}{i}\) 表示从 \(d\) 对中选出 \(i\) 对作为完整对,\(P(M,i)\) 表示选 \(i\) 种颜色并排列给这 \(i\) 对。
最终答案为 \((\displaystyle \sum_{i=0}^{\min(d, M)} \text{ans}_i)\)。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 3e5 + 5;
const int mod = 998244353;
map<int, int> mp;
int pos[M];
int qpow(int x, int y) {
int res = 1;
while (y) {
if (y & 1) res = res * x % mod;
x = x * x % mod;
y >>= 1;
}
return res;
}
int fac[M], inv[M];
void init(){
fac[0] = 1;
for (int i = 1; i < M; i++) fac[i] = fac[i - 1] * i % mod;
inv[M - 1] = qpow(fac[M - 1], mod - 2);
for (int i = M - 2; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
}
int C(int n, int k){
if (k < 0 || k > n) return 0;
return fac[n] * inv[k] % mod * inv[n - k] % mod;
}
int P(int n, int k){
if (k < 0 || k > n) return 0;
return fac[n] * inv[n - k] % mod;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m, sum = 0, t;
cin >> n >> m;
init();
pos[1] = 0, mp[0] = 1;
for (int i = 2, x; i <= n; i++) {
cin >> x;
sum += x;
mp[sum]++;
pos[i] = sum;
}
cin >> t;
int S = sum + t;
if (n < 3 || S % 2 == 1) {
cout << qpow(m, n);
return 0;
}
int half = S / 2, d = 0, ans = 0;
for (int i = 1; i <= n; i++) {
int want = (pos[i] + half) % S;
if (mp.count(want)) d++;
}
d /= 2;
for (int i = 0; i <= d; i++) {
if (i > m) break;
if (d - i > 0 && m - i - 1 < 0) continue;
int tmp = C(d, i) * P(m, i) % mod;
tmp = tmp * qpow(m - i, n - d - i) % mod;
tmp = tmp * qpow(m - i - 1, d - i) % mod;
ans = (ans + tmp) % mod;
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号