AtCoder ABC 216

A. Signed Difficulty

解题思路

签到

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    string s;
    cin >> s;
    string x = s.substr(0, s.find('.'));
    int y = stoi(s.substr(s.find('.') + 1));
    if (y <= 2) {
        x += '-';
    } else if (y <= 6) {
        
    } else {
        x += '+';
    }
    cout << x << '\n';
    
    return 0;
}

B. Same Name

解题思路

签到

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    map<pair<string, string>, int> mp;
    int n;
    cin >> n;
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        string s, t;
        cin >> s >> t;
        ans += mp[{s, t}];
        mp[{s, t}]++;
    }
    cout << (ans ? "Yes" : "No") << '\n';
    
    return 0;
}

C - Many Balls

题意

给你一个数 \(N\),你一开始手中的数字是 \(0\),你每次可以选择给这个数 \(+1\) 或者 \(\times 2\)
请你构造出一个长度不超过 \(120\) 的操作字符串,使得按照这种操作可以使你手中的数变成 \(N\)

解题思路

我们发现 \(+1\)\(\times 2\) 操作,和我们二进制转十进制十分像,故我们想到把 \(N\) 拆成二进制考虑即可。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;


int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    LL n;
    cin >> n;
    for (int i = 60; i >= 0; i--) {
        cout << 'B';
        if (n >> i & 1) {
            cout << 'A';
        }
    }

    return 0;
}

D. Pair of Balls

题意

给你 \(m\) 个放球的筒,一共有 \(2N\) 个球,\(N\) 种颜色,每种颜色的球恰好有 \(2\) 个,现在你可以
每次从 \(m\) 个球筒中任意两个不为空的球筒的顶部取球,若这两个球颜色一样,则把他们取走,否则不能取走,请问你最后可以按照这样的操作把所有球取出来吗?

解题思路

这里需要把问题转化为图论问题,我们可以把 \(n\) 种颜色看成点,然后球筒内部的顺序看成边
问题就变成了一共拓扑排序问题;还有一种思路就是,直接考虑 dfs,考虑连锁反应,每有一种颜色
出现了两次,就取出,加入下一个位置的球,递归下去,看看最后是否取完。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;


int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> a(m + 1), b(n + 1);
    int cnt = n;
    function<void(int)> dfs = [&](int u) {
        int w = a[u].back();
        b[w].push_back(u);
        if (b[a[u].back()].size() == 2) {
            cnt--;
            for (auto v : b[w]) {
                a[v].pop_back();
                if (!a[v].empty()) {
                    dfs(v);
                }
            }
        }
    };  
    for (int i = 1; i <= m; i++) {
        int k;
        cin >> k;
        for (int j = 1; j <= k; j++) {
            int x;
            cin >> x;
            a[i].push_back(x);
        }
        reverse(a[i].begin(), a[i].end());
        dfs(i);
    }
    cout << (cnt == 0 ? "Yes" : "No") << '\n';

    return 0;
}

E. Amusement Park

题意

你有 \(n\) 个景点,每个景点都有一个快乐值 \(a_i\),你有 \(k\) 次访问机会,每次你访问一个景点
之后,会使得该景点的快乐值降低一点,即令 \(a_i=a_i-1\),问你最终能获得的最大快乐值是多少?

数据范围

\(1\leq n \leq 10^5\)\(1\leq k, a_i \leq 2\times 10^9\)

解题思路

首先我们有个贪心思路就是,每次都取快乐值最大的,我们可以用个堆动态维护,但是我们发现
\(k\) 太大了,我们显然不能直接用堆模拟。我们考虑一个临界值,即最后我取的那个数的值是多少?
我们发现他必定会取前 \(k\) 大,我们可以先找到第 \(k\) 大的数是啥,这是一个经典的二分问题
我们只要找到这个 \(x\),然后算一下价值,最后注意处理一下多加的一些 \(x\),减去即可。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef __int128 i128;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int a[N];
  
template <typename T> void print(T x) {
    if (x < 0) { putchar('-'); print(x); return ; }
    if (x >= 10) print(x / 10);
    putchar((x % 10) + '0');
}

int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    LL l = 1, r = 2e9;
    auto calc = [&](LL x) {
        LL res = 0;
        for (int i = 1; i <= n; i++) {
            res += max(0LL, a[i] - x + 1);
        }
        return res;
    };
    while (l < r) {
        LL mid = (l + r + 1) >> 1;
        if (calc(mid) >= k) l = mid;
        else r = mid - 1;
    }
    i128 ans = 0;
    LL res = 0;
    for (int i = 1; i <= n; i++) {
        int m = max(0LL, a[i] - l + 1);
        res += m;
        ans += (l + a[i]) * m / 2;
    }
    ans -= 1LL * l * max(0LL, res - k);
    print(ans);
    putchar('\n');

    return 0;
}

F. Max Sum Counting

题意

给你两个序列 \(A\)\(B\),现在请你选出一个 \(\{1,2,..,N\}\) 的非空子集 \(S\),然后满足如下条件

  • \(max_{i\in S}A_i \geq \sum_{i\in S}B_i\)
    请问你可以选出多少种不同的子集,请你输出答案对 \(998244353\) 取模的结果。

数据范围

\(1\leq N,A_i,B_i\leq 5000\)

解题思路

首先观察数据范围以及计数类问题,我们很容易想到 DP ,由于涉及到最大值,我们考虑先让序列
\(A_i\) 的值从小到大排序,那么我们有个想法就是,我们统计答案,只要枚举我们最后一个选的
\(A_i\) 在哪即可。我们不难想到背包 DP,我们考虑 \(dp[i][j]\) 表示考虑前 \(i\) 个数,当且选的数
\(B_i\) 之和为 \(j\),注意这里 \(j\) 最大不会超过 \(5000\),因为一旦超过五千,就找不到一个合法的最大值的 \(A_i\) 满足条件。故我们只要按照 \(01\) 背包 的转移,枚举每一个位置选还是不选,如果选择就统计 \(ans\)

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int mod = 998244353, N = 5010;
int dp[N][N];
PII c[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> c[i].first;
    } 
    for (int i = 1; i <= n; i++) {
        cin >> c[i].second;
    }
    sort(c + 1, c + 1 + n);
    int ans = 0;
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++) {        
        for (int j = 0; j < N; j++) {
            auto [a, b] = c[i];
            dp[i][j] = dp[i - 1][j];
            if (j >= b) {
                if (j <= a) {
                    ans = (ans + dp[i - 1][j - b]) % mod;
                }
                dp[i][j] = (dp[i][j] + dp[i - 1][j - b]) % mod;
            }
        }
    }
    cout << ans << '\n';

    return 0;
}

G. 01Sequence

题意

给你 \(m\) 个限制,每个限制表示 \([l_i,r_i]\) 之间至少 \(x\)\(1\),现在请你构造一个长度为 \(n\)
\(01\) 序列,使得它满足所有 \(m\) 个限制,且要求序列中 \(1\) 的个数尽可能少。

数据范围

\(1\leq n \leq 2\times 10^5\)\(1\leq m \leq (2\times 10^5, n(n+1) / 2)\)
\(1\leq 1\leq l_i \leq r_i \leq n\)\(1\leq x_i \leq r_i - l_i + 1\)

解题思路

本题有两种思路:
(1) 差分约束
我们考虑前缀和,令 \(s_i\) 表示前 \(i\) 个位置 \(1\) 的个数之和,那么每个限制相当于 \(s_r - s_{l - 1} \geq x\)
而且题目需要我们构造一组解,且要 \(1\) 个数尽可能少,我们就可以想到这是差分约束的经典应用,我们只要按照如下几个限制关系建图:

  • \(s_r - s_{l - 1} \geq x\)
  • \(0\leq s_i - s_{i - 1} \leq 1\)
  • \(s_i \geq 0\)
  • \(s_0 = 0\)
    然后跑最长路,即可求出所有 \(s_i\) 的最小值,然后就可以知道每个位置的值即为:\(s_i-s_{i-1}\)

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
vector<PII> g[N];
int d[N];
bool st[N];

int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    while (m--) {
        int l, r, x;
        cin >> l >> r >> x;
        //s[r] - s[l - 1] >= x
        //s[r] - s[l - 1] <= r - l + 1
        //s[r] + l - r - 1 <= s[l - 1]
        g[l - 1].push_back({r, x});
        g[r].push_back({l - 1, l - r - 1});
    }
    g[n + 1].push_back({0, 0});
    g[0].push_back({n + 1, 0});
    //s[i] >= s[i - 1]
    //s[i] - s[i - 1] <= 1
    for (int i = 1; i <= n; i++) {
        //s[i] >= 0
        g[n + 1].push_back({i, 0});
        g[i - 1].push_back({i, 0});
        g[i].push_back({i - 1, -1});
    }
    queue<int> q;
    memset(d, -0x3f, sizeof d);
    d[n + 1] = 0;
    q.push(n + 1);
    st[n + 1] = true;
    while (q.size()) {
        int u = q.front();
        q.pop();
        st[u] = false;
        for (auto [v, w] : g[u]) {
            if (d[v] < d[u] + w) {
                d[v] = d[u] + w;
                if (!st[v]) {
                    st[v] = true;
                    q.push(v);
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        cout << d[i] - d[i - 1] << " \n"[i == n];
    }

    return 0;
}

(2) 贪心 + 并查集 + 树状数组
首先我们可以考虑先对限制按右端点从小到大排序,然后对于一个限制 \([l_i,r_i]\) 每次贪心的先尽可能靠右放,这样可以让后面的限制所放的 \(1\) 变少,故我们可以采用单链表式的并查集,维护每个位置往左第一个能放的位置,
然后每次用树状数组单点更新已经放 \(1\) 的位置,然后区间查询 \(1\) 的个数。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int p[N], ans[N];
array<int, 3> seg[N];
int c[N];
void updata(int x, int k) {
    for (int i = x; i < N; i += i & -i) {
        c[i] += k;
    }
}
int ask(int x) {
    int res = 0;
    for (int i = x; i; i -= i & -i) {
        res += c[i];
    }
    return res;
}
int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}
bool cmp(array<int, 3> &x, array<int, 3> &y) {
    return x[1] < y[1];
}

int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int l, r, x;
        cin >> l >> r >> x;
        seg[i] = {l, r, x};
    }
    for (int i = 0; i <= n; i++) {
        p[i] = i;
    }
    sort(seg + 1, seg + 1 + m, cmp);
    for (int i = 1; i <= m; i++) {
        auto [l, r, w] = seg[i];
        int cnt = w - (ask(r) - ask(l - 1));
        int u = find(r);
        for (int j = 1; j <= cnt; j++) {
            updata(u, 1), ans[u] = 1;
            p[u] = u - 1;
            u = find(u - 1);
        }
    }
    for (int i = 1; i <= n; i++) {
        cout << ans[i] << " \n"[i == n];
    }

    return 0;
}

posted @ 2023-08-05 14:40  jackle  阅读(29)  评论(0)    收藏  举报