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是为了美观而存在的):
image
可以看到, 它们并没有相交.

但我们改变一下相对位置, 就会惊奇地发现, 他们居然能相交!
image
又因为题目要求最大相交, 所以答案等于满足 \(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)\)

posted @ 2022-05-11 15:00  聂天泽  阅读(226)  评论(3编辑  收藏  举报