Codeforces Round #790 (Div. 4) 题解
A. Lucky?
没什么好说的, 直接模拟即可.
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e5 + 10;
int n, a[N];
string s;
int main() {
n = read();
while(n--) {
cin >> s;
if(s[0] + s[1] + s[2] == s[5] + s[4] + s[3]) puts("YES");
else puts("NO");
}
return 0;
}
复杂度: \(O(n)\)
B. Equal Candies
由于每一盒的糖果只能增不能减, 并且要让每一盒糖果都一样, 显然直接让每一盒都等于 最小的糖果数 , 于是答案就等于 \(\sum _{i = 1} ^n a_i - n * \mathop{min\{a_i\}}\limits_{1 \leq i \leq n}\)
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e5 + 10;
int t, n, now, m, sum;
int main() {
t = read();
while(t--) {
n = read();
sum = 0; m = 0x3f3f3f3f;
for(int i = 1; i <= n; ++i) {
now = read();
m = min(m, now);
sum += now;
}
printf("%d\n", sum - n * m);
}
return 0;
}
复杂度: \(O(n)\)
C. Most Similar Words
先看数据范围: \(1 \leq t \leq 100, 2 \leq n \leq 50, 1 \leq m \leq 8\) .
这个范围非常小, 于是考虑暴力.首先 \(O(n^2)\) 遍历任意两个字符串, 再 \(O(m)\) 计算出距离,取最小值即可.
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e5 + 10;
int t, n, m, res;
string s[N];
int main() {
t = read();
while(t--) {
res = 0x3f3f3f3f;
n = read(); m = read();
for(int i = 1; i <= n; ++i) cin >> s[i];
for(int i = 1; i <= n ;++i) {
for(int j = i + 1; j <= n; ++j) {
int tot = 0;
for(int p = 0; p < m; ++p)
tot += abs(s[i][p] - s[j][p]);
res = min(res, tot);
}
}
printf("%d\n", res);
}
return 0;
}
复杂度: \(O(n^2m)\)
D. X-Sum
还是先看数据范围: \(1 \leq t \leq 1000, 1 \leq n \leq 200, 1 \leq m \leq 200, 1 \leq \sum nm \leq 4 \times 10^4\).
注意最后一条 \(nm\) 的限制, 明显提醒我们复杂度里有 \(nm\).
于是先遍历每一个格子就可以把 \(nm\) 弄出来, 再计算所有能攻击到的格子总和 \(O(n + m)\)
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e5 + 10;
int t, n, m, k, res, cnt, mp[205][205];
inline bool check(int x, int y) { return x >= 1 && x <= n && y >= 1 && y <= m; }//防止走出棋盘
int main() {
t = read();
while(t--) {
n = read(); m = read(); k = max(n, m);
res = 0;
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) mp[i][j] = read();
for(int i = 1; i <= n ;++i) {
for(int j = 1; j <= m; ++j) {
cnt = 0;
for(int p = 1; p <= k; ++p) {
if(check(i + p, j + p)) cnt += mp[i + p][j + p];
if(check(i - p, j - p)) cnt += mp[i - p][j - p];
if(check(i + p, j - p)) cnt += mp[i + p][j - p];
if(check(i - p, j + p)) cnt += mp[i - p][j + p];
}
res = max(res, cnt + mp[i][j]);
}
}
printf("%d\n", res);
}
return 0;
}
复杂度: \(O(nm(n+m))\)
E. Eating Queries
显然, 每次查询的时候, 他都会想吃掉 含糖量最高 的那一个, 所以我们需要从大到小排序.
又因为有多次查询, 所以我们不必每次都重新算一遍, 于是 \(O(n)\) 维护一个数组 \(d\) 使得 \(\forall 1 \leq i \leq n, d_i = d_{i - 1} + a_i(a\ is\ sorted)\)
那么每次查询的时候只需要找到最小的 \(d_i\) 满足 \(d_i \geq q(q\ is\ asking)\) , 看起来是不是很熟悉? 没错, 可以二分搜索, 所以对于每次询问只需要 \(log(n)\) 的时间了!
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e6 + 10;
int t, n, m, q, res, a[N], d[N];
inline bool check(int x, int y) { return x >= 1 && x <= n && y >= 1 && y <= m; }
int main() {
t = read();
while(t--) {
n = read(); m = read();
for(int i = 1; i <= n; ++i) a[i] = read();
sort(a + 1, a + 1 + n, greater<int>());
for(int i = 1; i <= n; ++i) d[i] = d[i - 1] + a[i];
while(m--) {
res = lower_bound(d + 1, d + 1 + n, read()) - d;
printf("%d\n", res == n + 1 ? -1 : res);
}
}
return 0;
}
复杂度: \(O(nqlogn)\)
F. Longest Strike
首先显然维护每个数出现的次数, 由于 \(1 \leq a_i \leq 10^9\) 所以不能使用桶, 于是使用 map \(n logn\) 统计(还有一个好处就是自动从小到大排序, 等下会用到).
然后再从小到大遍历 map , 由于 map 从小到大, 正好满足我们的需求, 可以顺理成章地查找一段最长的连续的区间, 然后更新.
思路很简单, 代码很复杂. /wn
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e6 + 10;
int t, n, m, l, r, maxl, minv, maxv, s, last, a[N];
//这里使用m来代表题目中的k, maxl代表当前最长的区间长度, minv最小的ai, maxv最大的ai, s当前区间的长度, last上一个元素
map<int, int> mp;
//key元素, value出现次数
int main() {
t = read();
while(t--) {
n = read(); m = read();
mp.clear(); maxl = 0; minv = 0x3f3f3f3f; maxv = 0; s = 0;
for(int i = 1; i <= n; ++i) a[i] = read(), ++mp[a[i]], minv = min(minv, a[i]), maxv = max(maxv, a[i]);
last = minv - 1;//第一次认为是连续的
for(auto v : mp) {
int i = v.first;
if(i == maxv && v.second >= m && maxl < s + 1 && i == last + 1) maxl = s + 1, l = i - s, r = i;//最后一个不一定更新的到, 需要特判, 但如果前面不连续, 这里也判不掉, 在输出的时候能解决
if(v.second >= m && i == last + 1) ++s;//出现次数合格, 并且满足连续
else {
if(maxl < s) maxl = s, l = last + 1 - s, r = last;//更新答案
if(v.second >= m) s = 1;//如果次数合格但只是不连续, 则可以作为新一个区间的开头
else s = 0;//否则啥也不是, 从零开始
}
last = i;//记录上一个元素
}
if(maxl) printf("%d %d\n", l, r);
else if(mp[maxv] >= m) printf("%d %d\n", maxv, maxv);//最后不连续, 但是次数合格, 并且前面没有任何一个次数合格的话, 输出最后一个
else puts("-1");//否则是真的无解
}
return 0;
}
复杂度: \(O(nlogn)\)
G. White-Black Balanced Subtrees
我们规定 W 代表 \(1\) , B 代表 \(0\) , \(a_u\) 表示 \(u\) 这个节点的状态(是 \(0\) 还是 \(1\)) , \(dp1_u\) 表示以 \(u\) 为根的子树含有 \(1\) 的个数, \(dp0_u\) 表示以 \(u\) 为根的子树含有 \(0\) 的个数, 不用脑子都能想出怎么转移:
\(dp1_u = \mathop{\sum dp_v} \limits_{v\ is\ u's\ son} + a_u\)
\(dp0_u = \mathop{\sum dp_v} \limits_{v\ is\ u's\ son} +\ !a_u\)
这里的 \(!\) 表示程序里的取反(原谅我不知道怎么表示)
最后如果 \(u\) 满足 \(dp1_u = dp0_u\) 时, 结果需要加 \(1\).
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e6 + 10;
int t, n, res, dp1[N], dp0[N];
bool f[N];
string s;
int cnt = -1, head[N];
struct edge {
int to, nxt;
} e[N << 1];
inline void add(int u, int v) {
e[++cnt].to = v;
e[cnt].nxt = head[u];
head[u] = cnt;
}
inline void adde(int u, int v) { add(u, v); add(v, u); }
inline void dfs(int u, int fa) {
if(f[u]) dp1[u] = 1;
else dp0[u] = 1;
for(int i = head[u]; ~i; i = e[i].nxt) {
int v = e[i].to;
if(v == fa) continue;
dfs(v, u);
dp1[u] += dp1[v];
dp0[u] += dp0[v];
}
if(dp1[u] == dp0[u]) ++res;
}
int main() {
t = read();
while(t--) {
n = read(); res = 0;
for(int i = 0; i <= n; ++i) head[i] = -1;
for(int i = 0; i <= n; ++i) dp1[i] = dp0[i] = 0;
for(int i = 2; i <= n; ++i) adde(i, read());
cin >> s;
for(int i = 0; i < n; ++i)
f[i + 1] = s[i] == 'W';
dfs(1, 0);
printf("%d\n", res);
}
return 0;
}
复杂度: \(O(n)\)
H1. Maximum Crossings (Easy Version)
这个题把线段换成点想必大家都会做, 那么怎么变换一下呢?
首先显然如果 \(i < j\) 并且 \(a_i > a_j\) 必定相交(这个也就是逆序对)
那有的人说了: 给的样例中不也有 \(a_i = a_j\) 相交的吗?
当然, 如果 \(i < j\) 并且 \(a_i = a_j\) 可能相交.
考虑如下情况(只考虑1和3两条线段, 2是为了美观而存在的):
可以看到, 它们并没有相交.
但我们改变一下相对位置, 就会惊奇地发现, 他们居然能相交!
又因为题目要求最大相交, 所以答案等于满足 \(i < j, a_i \geq a_j\) 的个数.
这题 \(1 \leq n \leq 1000\) , 范围较小, 所以可以用 \(O(n^2)\) 的双指针来做.
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e6 + 10;
int t, n, res, a[N];
int main() {
t = read();
while(t--) {
n = read(); res = 0;
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i <= n; ++i)
for(int j = i + 1; j <= n; ++j)
if(a[i] >= a[j]) ++res;
printf("%d\n", res);
}
return 0;
}
复杂度: \(O(n^2)\)
H2. Maximum Crossings (Hard Version)
这题与 H1 只差了一个\(1 \leq n \leq 2 \times 10^5\) .于是考虑优化.
这里我用的树状数组求的逆序对, 归并排序也可以做.
注意要开 long long
.
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
int x = 0; char c = getchar(); bool f = 1;
while(c < '0' || c > '9') { if(c == '-') f = 0; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return (f ? x : -x);
}
const int N = 1e6 + 10;
int t, n, res, len, a[N], tree[N];
inline int lowbit(int &x) { return x & -x; }
inline void updata(int x) { while(x <= n) { ++tree[x]; x += lowbit(x); } }
inline int pre(int x) { int res = 0; while(x) { res += tree[x]; x -= lowbit(x); } return res; }
inline int ask(int l, int r) { return pre(r) - pre(l - 1); }
signed main() {
t = read();
while(t--) {
n = read(); res = 0;
for(int i = 1; i <= n; ++i) a[i] = read(), tree[i] = 0;
for(int i = n; i >= 1; --i) {
res += ask(1, a[i]);
updata(a[i]);
}
printf("%lld\n", res);
}
return 0;
}
复杂度: \(O(nlogn)\)