原题链接
A
https://codeforces.com/contest/1999/problem/A 800
B
https://qoj.ac/contest/1794/problem/9310
C
https://codeforces.com/problemset/problem/2008/D 1100
D
https://qoj.ac/contest/1485/problem/8081
E
https://codeforces.com/contest/2002/problem/B 1000
F
https://codeforces.com/contest/1950/problem/F 1700
G
https://codeforces.com/contest/1954/problem/B 1200
H
https://codeforces.com/contest/1991/problem/D 1900
I
https://codeforces.com/contest/2020/problem/C 1400
A
太简单,直接看代码吧
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
int a; cin >> a;
cout << a / 10 + a % 10 << "\n";
}
}
B
由于题目求的是方案数取余2的值,即求方案数的奇偶性。
尝试画一个矩阵来描述
\(M[i][j] = [j \in [l_i, r_i]] (即当 j \in [l_i, r_i],时M[i][j] = 1,否则为0)\)
以第一个样例为例:
\( \left[ \begin {array}{} 1 & 1 & 0 & 0 & 0 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 0 & 0 & 0 \\ 1 & 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \\ \end{array} \right] \)
我们把第二列除了最后一行的1全改为0,值是不变的,这由于第五行只能选择2
手模一下可以推广,初等行变换不会影响最后的奇偶性
注意到,当有行全为0的时候,那么答案就是偶数,反之为奇数
即判断这个矩阵是否行满秩即可
我们考虑区间覆盖,问题转化为判断是否存在一个区间能被其他若干个区间覆盖
我们把l和r + 1建边,如果图中有环,则说明存在一个区间能被其他若干个区间覆盖
下面我用拓扑排序判环
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
int n;
cin >> n;
vector<vector<int>> adj(n + 2, vector<int>(0));
vector<int> d(n + 2);
auto addEdge = [&](int u, int v) {
adj[u].emplace_back(v);
adj[v].emplace_back(u);
d[u]++; d[v]++;
};
auto toposort = [&](vector<vector<int>> &adj, vector<int> &d) {
vector<int> tp;
queue<int> q;
for (int i = 1; i <= n + 1; ++ i) {
if (d[i] == 1) q.push(i);
}
while (q.size()) {
int x = q.front(); q.pop();
tp.push_back(x);
for (auto y : adj[x]) {
d[y] --; d[x] --;
if (d[y] == 1) q.push(y);
}
}
return (int)tp.size() == n + 1;
};
for (int i = 1; i <= n; ++ i) {
int l, r;
cin >> l >> r;
addEdge(l, r + 1);
}
cout << toposort(adj, d) << "\n";
}
}
C
由于原数组是排列,每一个点经过若干次,一定可以到本身,那么 \(F(i)\) 其实就是 \(i\) 所在的联通块的黑色的个数
我们用并查集合并即可得到连通块,把并查集的中的每个的点的权提前赋好再去合并
最后输出对应的连通块的值即可
#include <bits/stdc++.h>
using namespace std;
class DSU {
public:
int n;
vector<int> dsu, dsus;
// dsu[i]存是 i 的祖宗结点
// dsus[i]存 i 所在图的大小
DSU() {}
DSU(int n) { init(n); }
void init(int n) {
this -> n = n;
dsu.resize(n + 1);
dsus.resize(n + 1, 1);
iota(dsu.begin(), dsu.end(), 0);
}
int find(int a) {
if (a == dsu[a]) {
return a;
} else {
dsu[a] = find(dsu[a]);
return dsu[a];
}
}
void merge(int a, int b) {
int x = find(a);
int y = find(b);
if (x != y) {
if (dsus[x] < dsus[y]) { swap(x, y); }
dsu[y] = x; dsus[x] += dsus[y];
}
}
//由于未必路径压缩好了,所以求祖宗结点需调用find函数或者使用reboot()函数路径压缩
void reboot() {
for (int i = 1; i <= n; ++ i) {
int fa = find(dsu[i]);
dsu[i] = fa; dsus[i] = dsus[fa];
}
}
};
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
int n;
cin >> n;
vector<int> p(n + 1);
for (int i = 1; i <= n; ++ i) cin >> p[i];
DSU a(n);
for (int i = 1; i <= n; ++ i) {
char c;
cin >> c;
a.dsus[i] = (c - '0') ^ 1;
}
for (int i = 1; i <= n; ++ i) {
a.merge(i, p[i]);
}
a.reboot();
for (int i = 1; i <= n; ++ i) {
cout << a.dsus[i] << " \n"[i == n];
}
}
}
D
题目求 \(C_2\) 上的点到 \(C_1\) 上所有点的最小期望曼哈顿距离
设 \(C_2\) 上的点 \((x_0, y_0)\) 为使得 \(C_2\) 上的点到 \(C_1\) 上所有点的最小期望曼哈顿距离的点
设 \(EMD\) 为 \(C_1\) 到 \((x_0, y_0)\) 期望曼哈顿距离
设 \(MD(x, y)\) 为 \((x, y)\) 到 \((x_0, y_0)\) 曼哈顿距离
对于 \(C_1\) 上的点 \((x_1 + \Delta x, y_1 + \Delta y)\) 必然有 \((x_1 - \Delta x, y_1 - \Delta y)\) 使得
\(MD(x_1 + \Delta x, y_1 + \Delta y) = MD(x_1 - \Delta x, y_1 - \Delta y)\)
所以 \(EMD = MD(x_1, y_1)\)
即到 \((x_0, y_0)\) 期望曼哈顿距离就是 \(C_1\) 的圆心到 \((x_0, y_0)\) 的曼哈顿距离
那么我们只需要最小化这个曼哈顿距离即可
不妨反过来思考,我们在 \(C_2\) 上找一个点使得曼哈顿距离最小
在 \(C_1\) 的圆心为圆心画曼哈顿圆 \(MC_1\) ,当 \(MC_1\) 与 \(C_2\) 相切时,切点就是 \((x_0, y_0)\)
然后计算 \(MD(x_1, y_1, x_0, y_0)\) 即可
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
ll x11, y11, x12, y12;
ll x21, y21, x22, y22;
cin >> x11 >> y11 >> x12 >> y12;
cin >> x21 >> y21 >> x22 >> y22;
double x10 = (double)(x11 + x12) / 2.0, y10 = (double)(y11 + y12) / 2.0;
double x20 = (double)(x21 + x22) / 2.0, y20 = (double)(y21 + y22) / 2.0;
double d = sqrt(2ll * ((x22 - x21) * (x22 - x21) + (y22 - y21) * (y22 - y21))) / 4.0;
double x0, y0;
if (x10 < x20 && y10 < y20) {
x0 = x20 - d;
y0 = y20 - d;
} else if (x10 < x20 && y10 > y20) {
x0 = x20 - d;
y0 = y20 + d;
} else if (x10 > x20 && y10 < y20) {
x0 = x20 + d;
y0 = y20 - d;
} else if (x10 > x20 && y10 > y20) {
x0 = x20 + d;
y0 = y20 + d;
}
double ans = abs(x0 - x10) + abs(y0 - y10);
cout << fixed << setprecision(10) << ans << "\n";
}
}
E
推理可得,答案和Alice的选择无关,那么我们只需要遍历Alice的数组,每次判断当前的首尾是不是也是Bob数组的首尾即可
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
int n;
cin >> n;
vector<int> a(n + 1), b(n + 1);
for (int i = 1; i <= n; ++ i) cin >> a[i];
for (int i = 1; i <= n; ++ i) cin >> b[i];
bool flag = 1;
int l = 1, r = n;
for (int i = 1; i <= n; ++ i) {
if (a[i] == b[l] && a[n] == b[r]) {
l ++;
} else if (a[i] == b[r] && a[n] == b[l]) {
r --;
} else {
flag = 0;
break;
}
}
if (flag) {
cout << "Bob\n";
} else {
cout << "Alice\n";
}
}
}
F
根据二叉树的性质, \(n0 == n2 + 1\) ,若不符合则输出 \(-1\)
考虑贪心,优先把度为2的用完,再用1的
我们用队列模拟这个贪心的过程,队首为 \(x\) ,表示当前在第 \(x\) 层
所以如果需要往队列里添加节点的话 需要添加 \(x + 1\)
用 \(ans\) 记录最大层数即可
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
int n2, n1, n0;
cin >> n2 >> n1 >> n0;
int ans = -1;
if (n0 == n2 + 1) {
queue<int> q;
q.push(0);
ans = 0;
while (!q.empty()) {
int x = q.front(); q.pop();
ans = x;
if (n2) {
n2 --;
q.push(x + 1);
q.push(x + 1);
} else if (n1) {
n1 --;
q.push(x + 1);
}
}
}
cout << ans << "\n";
}
}
G
分析题目可得,由于原数组是“美丽”的,那么数组的首尾一定是相等的,并且其中有其他数字,也只是一个孤立的,因为如果有两个及以上,那么该数组就不“美丽”,自然地,只有把两个孤立的数字合并就可以了
要使两个孤立的数字合并,那么只需要删掉他们之间的所有数字就行
问题就被转化成了,最小的连续为k的子段,k即是数组头部的位置
注意当全部为一种时,要输出-1
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
int n;
cin >> n;
int ans = n, cur = 0;
vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) cin >> a[i];
for (int i = 1; i <= n; ++ i) {
if (a[i] != a[1]) {
ans = min(ans, cur);
cur = 0;
} else {
cur ++;
}
}
ans = min(ans, cur);
if (ans == n) ans = -1;
cout << ans << "\n";
}
}
H
看到要使直接连接的两个顶点没有相同的颜色,可以想到著名的四色问题
猜测至多只需要四种颜色就能填满
自然的,(在结点较多时)
我们给结点 1 染色 1
我们给结点 2 染色 2
我们给结点 3 染色 3
我们给结点 4 染色 4
5 可以选择 1 3 4
6 可以选择 2
7 可以选择 1 3
8 可以选择 1 2 4
目前不能找到什么规律,但是由于建边是根据异或的,我们可以尝试把5 6 7 8与其可以建边的点进行异或看看
5:
1 4
3 6
4 1
6:
2 4
7:
1 6
3 4
8:
1 9
2 10
4 12
可以发现5 6 7的异或值都有4
进一步思考
$xor(4n_1+k, 4n_2+k) = 4n (k \in {0, 1, 2, 3}, n \in N^*) $
即 \(4n+k\) 可以选用同一种颜色,因为 \(4n\) 一定是一个非质数,即这类点不会直接相连
由于题目需要输出最小的,将颜色不需要4种的特判即可
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
int n;
cin >> n;
if (n < 6) {
cout << n / 2 + 1 << "\n";
int ans = 1, t = 1;
for (int i = 1; i <= n; ++ i) {
cout << ans << " \n"[i == n];
ans += t % 2;
t = (t + 1) % 2;
}
} else {
cout << 4 << "\n";
for (int i = 1; i <= n; ++ i) {
cout << (i - 1) % 4 + 1 << " \n"[i == n];
}
}
}
}
I
我们将 \(a, b, c, d\) 全部二进制展开
对每一位有
a | a | a | a | a |
---|---|---|---|---|
b | 0 | 0 | 1 | 1 |
c | 0 | 1 | 0 | 1 |
ans | a | 0 | 1 | !a |
按照这个表算出 \(a\) 即可
更进一步,我们发现 \(a = xor(b, d)\)
然后判断 \(a\) 是否合法即可
下面给出两种解法
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
ll a, b, c, d;
cin >> b >> c >> d;
a = b ^ d;
if ((a | b) - (a & c) != d) a = -1;
cout << a << "\n";
}
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int tt = 1;
cin >> tt;
while (tt--) {
bool flag = 1;
ll a = 0, b, c, d;
cin >> b >> c >> d;
ll B = b, C = c, D = d;
vector<int> al(65);
for (int i = 0; i < 65; ++ i) {
if (b % 2ll == c % 2ll) {
al[i] = (d % 2ll) ^ (b % 2ll);
} else if (b % 2ll == 0 && c % 2ll == 1) {
al[i] = 1;
} else if (b % 2ll == 1 && c % 2ll == 0) {
} else {
flag = 0;
break;
}
b /= 2ll;
c /= 2ll;
d /= 2ll;
if (b == 0 && c == 0 && d == 0) break;
}
for (int i = 64; i >= 0; -- i) {
a = a * 2ll + al[i];
}
if ((a | B) - (a & C) != D) a = -1;
cout << a << "\n";
}
}
标签:2ll,ZZJCACM,int,题解,tt,cin,--,训练赛,using
From: https://www.cnblogs.com/Proaes/p/18624985