AtCoder Beginner Contest 282

A - Generalized ABC (abc282 a)

题目大意

给定\(n\),输出一个字符串,从'A','B','C'...拼接得到的长度为 \(n\)

解题思路

模拟即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    string s;
    for(int i = 0; i < n; ++ i)
        s += 'A' + i;
    cout << s << '\n';

    return 0;
}



B - Let's Get a Perfect Score (abc282 b)

题目大意

给定一个\(01\)矩阵。选两行,其每一列至少有一个 \(1\)。问满足要求的选法。

解题思路

范围不大,暴力即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<string> qwq(n);
    for(auto &i : qwq)
        cin >> i;
    int ans = 0;
    auto check = [&](int x, int y){
        int cnt = 0;
        FOR(i, 0, m){
            cnt += (qwq[x][i] == 'o' || qwq[y][i] == 'o');
        }
        return cnt == m;
    };
    FOR(i, 0, n)
        FOR(j, i + 1, n){
            ans += check(i, j);
        }
    cout << ans << '\n';


    return 0;
}



C - String Delimiter (abc282 c)

题目大意

给定一个字符串,要求把非"括起来的,换成.

解题思路

模拟即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    string s;
    cin >> n >> s;
    bool ok = false;
    for(auto &i : s){
        if (i == '\"')
            ok ^= 1;
        else if (i == ',' && !ok)
            i = '.';
    }
    cout << s << '\n';

    return 0;
}



D - Make Bipartite 2 (abc282 d)

题目大意

给定一张\(n\)个点的无向图,给其加一条边,满足其是二分图。问满足该条件的方案数。

解题思路

注意原图不一定连通。

对原图进行黑白染色,如果颜色冲突则答案为\(0\)

否则,对于一个连通块内,假设点数为\(cnt\),边数为 \(cntm\),染了\(w\)个白点和 \(b\)个黑点,则满足要求的方案数为 \(wb - cntm\)。该连通块还能连向其他连通块,且可以任意连,其方案数为\(cnt \times (n - cnt)\)

对所有连通块累计方案数即为答案。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> edge(n + 1);
    vector<int> deep(n + 1, -1);
    FOR(i, 0, m){
        int u, v;
        cin >> u >> v;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    LL ans = 0;
    vector<int> col(2, 0);
    bool ok = true;
    int cnt = 0;
    set<pair<int, int>> ee;
    function<void(int, int, int)> dfs = [&](int u, int fa, int cc){
        deep[u] = cc;
        col[cc] ++;
        ++ cnt;
        for(auto &v : edge[u]){
            if (v == fa)
                continue;
            ee.insert({u, v});
            ee.insert({v, u});
            if (deep[v] != -1){
                if (deep[v] == deep[u])
                    ok = false;
            }else 
                dfs(v, u, cc ^ 1);
        }
    
    };
    int block = 0;
    FOR(i, 1, n + 1){
        if (deep[i] == -1){
            ++ block;
            cnt = 0;
            col[0] = col[1] = 0;
            ee.clear();
            dfs(i, i, 0);
            int cntm = ee.size() / 2;
            ans += 1ll * col[0] * col[1] - cntm;
            ans += 1ll * cnt * (n - cnt);
            n -= cnt;
        }
    }
    if (!ok)
        ans = 0;
    cout << ans << '\n';

    return 0;
}



E - Choose Two and Eat One (abc282 e)

题目大意

给定\(n\)个数,每个数范围\([1, m - 1]\),每次选择两个数\(x,y\),获得 \(x^y + y^x \mod m\) 分数。再将其中一个数丢弃,直至剩一个数。问获得的分数最大值。

解题思路

对于每个数,可能被选择多次,其中仅一次会被干掉,其他的都能保留。一个与多个的关系与树节点的一个父亲和多个儿子非常类似。

给定一棵树,题意操作相当于每次将叶子节点去掉,同时获得叶子与父亲的边权值。

对原图跑一边最大生成树即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)

int n, m;

LL qpower(LL a, LL b){
    LL qwq = 1;
    while(b){
        if (b & 1)
            qwq = qwq * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return qwq;
}

LL calc(LL x, LL y){
    return (qpower(x, y) + qpower(y, x)) % m;
}

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    cin >> n >> m;
    vector<vector<LL>> dis(n + 1, vector<LL>(n + 1, 0));
    vector<LL> a(n + 1);
    for(int i = 1; i <= n; ++ i){
        cin >> a[i];
    }
    vector<pair<LL, pair<int, int>>> edge;
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= n; ++ j)
            edge.push_back({calc(a[i], a[j]), {i, j}});
    vector<int> fa(n + 1);
    function<int(int)> findfa = [&](int x){
        return fa[x] == x ? x : fa[x] = findfa(fa[x]);
    };
    iota(fa.begin(), fa.end(), 0);
    sort(edge.begin(), edge.end(), [&](const pair<LL, pair<int, int>>& a, const pair<LL, pair<int, int>>& b){
            return a.first > b.first;
            });
    LL ans = 0;
    for(auto &i : edge){
        int fu = findfa(i.second.first);
        int fv = findfa(i.second.second);
        if (fu != fv){
            fa[fu] = fv;
            ans += i.first;
        }
    }
    cout << ans << '\n';

    return 0;
}



F - Union of Two Sets (abc282 f)

题目大意

交互题。给定\(n\),要求生成 \(m\)个区间,并用这些区间回答 \(q\)组询问。

每组询问给定 \(l,r\),要求从 \(m\)个区间选出 \(2\)个区间,其并集为区间\([l,r]\)

\(n \leq 4000, m \leq 500000\)

解题思路

其实\(RMQ\)里的\(ST\)表的做法就能达到题目所述的要求。

即为每个左端点生成长度为\(2\)的幂的区间。

设长度 \(len = r - l + 1\) ,其最大的小于等于\(len\)\(2\)的幂为 \(x\)

则选择的区间为 \([l, l + x - 1], [r - x + 1, r]\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)

inline int highbit(int x) { return 31 - __builtin_clz(x); }

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    map<pair<int, int>, int> area;
    int m = 0;
    for(int i = 1; i <= n; ++ i){
        for(int j = 1; i + j - 1 <= n; j <<= 1){
            ++ m;
            area[{i, i + j - 1}] = m;
        }
    }
    cout << area.size() << '\n';
    for(auto &i : area){
        cout << i.first.first << ' ' << i.first.second << '\n';
    }
    cout.flush();
    int q;
    cin >> q;
    while(q--){
        int l, r;
        cin >> l >> r;
        int t = highbit(r - l + 1);
        cout << area[{l, l + (1 << t) - 1}] << ' ' << area[{r - (1 << t) + 1, r}] << endl;
    }

    return 0;
}



G - Similar Permutation (abc282 g)

题目大意

定义两个排列的相似度为,差分数组下符号相同的标号数量。

给定\(n,k,p\),问 长度为 \(n\)的相似度为 \(k\)的排列对数,对\(p\)取模。

解题思路

起初想的是从小到大插入每个数统计方案,但是一旦插入到前面的位置,就会影响到后面的相似度判断,需要记录前面的升降趋势状态数会非常大,该方向不可行。

然后考虑按一位一位填数。要知道该位填的数与上一位关系的大小关系,需要记录上一位数是多少,但这样的话也得记录目前已经填了哪些数,这样才能知道有多少种填法是小于上一个数。而已经填了哪些数这一状态是指数级的。局面一度陷入僵局。

细细一想,我们需要的状态:上一个数填的是什么,以及当前填了哪些数,是为了决定最后两个数字的大小关系(这关系到相似度是否\(+1\)),而并不关心这两个数具体是多少,我们只想知道,有多少种填法是小于,有多少种填法是大于

因此我们可以设计的状态是:当前已填的最后一个数在未填的数中排名第几位。通过这一状态,我们就知道:有多少种填法是小于,有多少种填法是大于

\(dp[i][j][a][b]\)表示我们填了 \(i\)个数,两个排列的相似度为 \(j\), 第一个排列的最后一个数在未填数中排名为\(a\),第二个排列的最后一个数在未填数中排名为 \(b\)的方案数。

\[dp[i][j][a][b] = \sum_{x \leq a, y > b}dp[i - 1][j][x][y] + \sum_{x > a, y \leq b}dp[i - 1][j][x][y] \]

\[dp[i][j][a][b] = \sum_{x \leq a, y \leq b}dp[i - 1][j - 1][x][y] + \sum_{x > a, y > b}dp[i - 1][j - 1][x][y] \]

即新填一个数

  • 相似度不增加,则是一小一大和一大一小两种情况
  • 相似度加一,则是一小一小和一大一大两种情况

朴素转移是\(O(n^2)\),可以用二维前缀和优化成 \(O(1)\),因此总的时间复杂度为 \(O(n^4)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const int N = 100 + 8;

int n, k, p, cur;
int dp[2][N][N][N], sum[N][N];

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    cin >> n >> k >> p;
    cur = 0;
    for(int i = 1; i <= n; ++ i)
        for(int j = 1; j <= n; ++ j)
            dp[cur][0][i][j] = 1;
    for(int i = 2; i <= n; ++ i){
        cur ^= 1;
        memset(dp[cur], 0, sizeof(dp[cur]));

        for(int j = 0; j <= k; ++ j){
            int up = n - i + 2; // 未填数数量+1,即排名的最大值

            for(int a = 1; a <= up; ++ a)
                for(int b = 1; b <= up; ++ b){
                    sum[a][b] = (0ll + sum[a - 1][b] + sum[a][b - 1] - sum[a - 1][b - 1] + dp[cur ^ 1][j][a][b]) % p;
                    if (sum[a][b] < 0)
                        sum[a][b] += p;
                }

            for(int a = 1; a <= up; ++ a)
                for(int b = 1; b <= up; ++ b){
                    dp[cur][j][a][b] = (0ll + dp[cur][j][a][b] + sum[a][up] - sum[a][b] + sum[up][b] - sum[a][b]) % p;
                    if (dp[cur][j][a][b] < 0)
                        dp[cur][j][a][b] += p;

                    dp[cur][j + 1][a][b] = (0ll + dp[cur][j + 1][a][b] + sum[a][b] + sum[up][up] - sum[a][up] - sum[up][b] + sum[a][b]) % p;
                    if (dp[cur][j + 1][a][b] < 0)
                        dp[cur][j + 1][a][b] += p;
                }
        }
    }
    cout << dp[cur][k][1][1] << '\n';

    return 0;
}



Ex - Min + Sum (abc282 h)

题目大意

给定两个长度为\(n\)的数组\(A,B\)和一个整数\(s\)。问有多少对 \((l,r)\),满足

\[\min_{l \leq i \leq r} A_i + \sum_{i = l}^{r} B_i \leq s \]

解题思路

条件有两项,我们先考虑第一项。

考虑\(a[i]\)作为其区间的最小值,那么该区间 \([l,r]\)需要满足一定条件。

用单调栈预处理 \(L[i],R[i]\)数组分别表示 \(a[i]\)左边第一个(最靠近的)小于等于 \(a[i]\)的下标,以及\(a[i]\)右边第一个小于 \(a[i]\)的下标, 那么满足\(L[i] < l \leq i \leq r < R[i]\)的区间 \([l,r]\)的最小值都是 \(a[i]\),这样就确定好了第一项。

考虑和式,首先通过前缀和将和式变成两数相减的形式。因为前缀和是单调递增的,如果我们枚举\(l\),通过二分可以找到符合条件的 \(r\),同理枚举 \(r\),通过二分也能找到符合条件的 \(l\)

\(l\)\(r\)的枚举个数(即\(i - l\)\(r - i\)的大小),取较小的那边枚举二分求解即可。咋一看这做法的复杂度可能有\(O(n^2\log n)\)的量级,但可以证明这样的复杂度其实是\(O(n\log^2 n)\)

考虑全局最小值,其枚举范围是整个数组。考虑完后该最小值将整个数组切分成两部分,然后递归考虑左边的最小值和右边的最小值。这里有分治的想法,这里我们主要关注合并。

考察每个端点被枚举的次数。因为它所在的区间是长度较小的,当枚举完后,下一个枚举到它的区间的长度(左右合并成一个大区间)至少翻倍,最多翻倍\(\log\)次就到长度为 \(n\)了。所以每个端点被枚举的次数是 \(\log\)次,加上每次枚举的二分有一个\(\log\)。因此总的时间复杂度为 \(O(n \log^2 n)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n;
    LL s;
    cin >> n >> s;
    vector<LL> 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];
    vector<LL> sumb(n + 1);
    partial_sum(b.begin() + 1, b.end(), sumb.begin() + 1);

    vector<int> l(n + 1), r(n + 1);
    stack<int> pos;

    for(int i = 1; i <= n; ++ i){
        while(!pos.empty() && a[pos.top()] >= a[i]){
            r[pos.top()] = i - 1;
            pos.pop();
        }
        pos.push(i);
    }
    while(!pos.empty()){
        r[pos.top()] = n;
        pos.pop();
    }

    for(int i = n; i >= 1; -- i){
        while(!pos.empty() && a[pos.top()] > a[i]){
            l[pos.top()] = i + 1;
            pos.pop();
        }
        pos.push(i);
    }
    while(!pos.empty()){
        l[pos.top()] = 1;
        pos.pop();
    }

    auto solve = [&](int l, int mid, int r){
        LL cnt = 0;
        int len1 = mid - l + 1;
        int len2 = r - mid + 1;
        vector<LL>::iterator sb = sumb.begin(), st = sb + l - 1, midd = sb + mid, en = sb + r + 1;
        LL up = s - a[mid];
        if (len1 < len2){
            for(auto it = st; it != midd; it = next(it)){
                auto pos = upper_bound(midd, en, up + *it);
                cnt += pos - midd;
            }
        }else{
            for(auto it = midd; it != en; it = next(it)){
                auto pos = lower_bound(st, midd, *it - up);
                cnt += midd - pos;
            }
        }
        return cnt;
    };

    LL ans = 0;
    for(int i = 1; i <= n; ++ i){
        ans += solve(l[i], i, r[i]);
    }

    cout << ans << '\n';

    return 0;
}



posted @ 2022-12-18 13:49  ~Lanly~  阅读(632)  评论(6编辑  收藏  举报