Atcoder ABC 384

Atcoder ABC 384

C 排序
D 前缀和,循环数组
E 优先队列搜索
F 数论
G 莫队

C

高桥决定举办一场程序设计竞赛。

此次竞赛包含五个问题:分别标记为A、B、C、D、E,对应的分数分别为a、b、c、d、e。

共有31位参赛者,且每位参赛者至少解决了一个问题。

更具体地说,对于字符串ABCDE的任意非空子序列(不必连续),存在一位以该子序列命名的参赛者,他们解决了与他们名字中字母对应的问题,而未解决其他问题。

例如,名为A的参赛者仅解决了问题A,而名为BCE的参赛者解决了问题B、C和E。

请按照获得分数从高到低的顺序打印参赛者的名称。参赛者获得的分数是他们所解决问题的分数之和。

若两位参赛者得分相同,请优先打印名字在字典序中较小的参赛者的名称。


位操作排序

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

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;

int a[5];
vector<pair<string, int>> s;


int main(){
    //freopen("in.txt", "r", stdin);
    for (int i = 0; i < 5; ++i) cin >> a[i];
    for (int i = 1; i < 32; ++i) {
        int score = 0;
        string name = "";
        for (int j = 0; j < 5; ++j) {
            if (i & (1 << j)) {
                score += a[j];
                name += char('A' + j);
            }
        }
        s.push_back({ name, score });
    }
    sort(s.begin(), s.end(), [](pair<string, int> x, pair<string, int> y) {
        if (x.second == y.second) {
            return x.first < y.first;
        }
        return x.second > y.second;
        });
    for (auto [name, score] : s) {
        cout << name << endl;
    }
    return 0;
}

D

一个无限序列A的N项A1,A2,...,ANA_1, A_2, ..., A_NA1,A2,...,AN,这个序列的周期为N。

判断是否存在一个非空的连续子序列,其和为S。


首先记A的和为k
如果S整除k,那么一定存在,也就是循环S/k次
如果不整除k,那么要看剩余的余数r
手操后发现,r是某个区间和,且这个区间可以是[6,7,8,1,2,3]这样的循环区间
因此开两倍长度数组,求新数组中是否有某个区间和满足和为r
这个问题可以用前缀和+哈希来做。

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

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;

int n;
ll S;
ll a[400002];


int main(){
    //freopen("in.txt", "r", stdin);
    cin >> n >> S;
    ll k = 0;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        k += a[i];
    }
    for (int i = n + 1; i <= 2 * n; ++i) {
        a[i] = a[i - n];
    }
    if (S % k == 0) {
        printf("Yes\n");
        return 0;
    }
    else {
        ll r = S % k;
        unordered_map<ll, bool> hs;
        ll c = 0;
        bool ok = 0;
        hs[0] = 1;
        for (int i = 1; i <= 2 * n; ++i) {
            c += a[i];
            if (hs.count(c - r)) {
                ok = 1;
                break;
            }
            hs[c] = 1;
        }
        if (ok) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

E

有一个由H行和W列组成的网格。用(i,j)表示从顶部数第i行(1≤i≤H)和从左侧数第j列(1≤j≤W)的单元格。

最初,在单元格(i,j)中有一个强度为Si,jS_{i,j}Si,j的史莱姆,而高桥是位于单元格(P,Q)的史莱姆。

执行以下操作任意次数(可能为零次)后,找出高桥可能的最大强度:

在他相邻的史莱姆中,选择一个强度严格小于他强度X倍的史莱姆并吸收它。作为结果,被吸收的史莱姆消失,高桥的强度增加被吸收史莱姆的强度。
执行上述操作时,消失的史莱姆留下的空位会立即被高桥填补,原本与消失史莱姆相邻的史莱姆(如果有的话)将成为与高桥新相邻的史莱姆


可以看到,每次选取的时候都应该尽可能先选取周围大的
假设当前的强度为x,周围有两个a>b的史莱姆
如果选小的能满足x+b>ak则x+a=x+b+a-b>ak+a-b>bk
所以应该先选大的
那么就做一个优先队列bfs。

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

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;

int h, w;
ll x;
int r, c;
ll s[505][505];
bool b[505][505];
struct Item {
    ll w;
    int r, c;
};
int dirs[4][2] = {
    {0, 1}, {1, 0}, {0, -1}, {-1, 0}
};

vector<Item> neighbors(const Item& cur) {
    vector<Item> ret;
    for (auto d : dirs) {
        int nr = cur.r + d[0], nc = cur.c + d[1];
        if (nr >= 1 && nr <= h && nc >= 1 && nc <= w && b[nr][nc] == 0) {
            b[nr][nc] = 1;
            ret.push_back(Item{ s[nr][nc], nr, nc });
        }
    }
    return ret;
}


int main(){
    //freopen("in.txt", "r", stdin);
    cin >> h >> w >> x;
    cin >> r >> c;
    for (int i = 1; i <= h; ++i) {
        for (int j = 1; j <= w; ++j) {
            cin >> s[i][j];
        }
    }
    b[r][c] = 1;
    auto cmp = [](Item x, Item y) {
        return x.w > y.w;
    };
    priority_queue<Item, vector<Item>, decltype(cmp)> qu(cmp);
    for (auto &ni : neighbors(Item{ s[r][c], r, c })) {
        qu.push(ni);
    }
    ll tot = s[r][c];
    while (qu.size()) {
        Item cur = qu.top();
        qu.pop();
        if (tot / x > cur.w || (tot / x == cur.w && tot % x) ) {
            tot += cur.w;
        }
        else {
            break;
        }
        for (auto &ni : neighbors(cur)) {
            qu.push(ni);
        }
    }
    printf("%lld\n", tot);
    return 0;
}

F

对于正整数xxx,定义函数f(x)f(x)f(x)如下:“当xxx为偶数时,持续将其除以 2,直到xxx不再为偶数。这些除法操作后的最终值即为f(x)f(x)f(x)。” 例如,f(4)=f(2)=f(1)=1f(4) = f(2) = f(1) = 1f(4)=f(2)=f(1)=1,以及f(12)=f(6)=f(3)=3f(12) = f(6) = f(3) = 3f(12)=f(6)=f(3)=3

给定长度为NNN的整数序列A=(A1,A2,…,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,,AN),计算∑i=1N∑j=iNf(Ai+Aj)\sum_{i=1}^{N} \sum_{j=i}^{N} f(A_i + A_j)i=1Nj=iNf(Ai+Aj)


首先,这个和是一个累加,对于每个AiA_iAi来说,可以将前面的所有AjA_jAj记录下来
但是每个和都需要唯一表示成q∗2pq*2^pq2p的形式,因此需要对每个数除2p2^p2p的余数做记录,这样可以用hash来计算Ai+AjA_i+A_jAi+Aj是否能被2p2^p2p整除。
例如12=3∗2212=3*2^212=322这里12归结在4这一组里面
但是一个数能被4整除必然能被2整除,如何唯一表示x可以被4整除但不能被2整除呢?
答案是x%8==4
所以在计算是否能恰好被2p2^p2p整除时我们需要维护除2p+12^{p+1}2p+1的余数
下面的代码里面用了unordered_map,时间上还有优化的空间。

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

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;

const int N = 26;

unordered_map<ll, ll> r0[N], c0[N], r1[N], c1[N];
ll pw[N];
int n;
ll a[200020];


int main(){
    //freopen("in.txt", "r", stdin);
    pw[0] = 1;
    for (int i = 1; i < N; ++i) {
        pw[i] = pw[i - 1] * 2ll;
    }
    cin >> n;

    ll ans = 0;
    ll a2 = 0;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        ll cur = 0;
        for (int j = N - 2; j >= 0; --j) {
            ll q = a[i] % pw[j + 1];
            if (q <= pw[j]) {
                ll r = pw[j] - q;
                if (r0[j + 1].count(r)) {
                    ll t = r0[j + 1][r] * 2 + (a[i] / pw[j]) * c0[j + 1][r];
                    if (r) t += c0[j + 1][r];
                    cur += t;
                }
            }
            else {
                ll r = pw[j] + pw[j + 1] - q;
                if (r1[j + 1].count(r)) {
                    ll t = r1[j + 1][r] * 2 + (a[i] / pw[j] + 2) * c1[j + 1][r];
                    cur += t;
                }
            }
        }
        for (int j = 1; j < N; ++j) {
            ll q = a[i] % pw[j];
            if (q > pw[j - 1]) {
                r1[j][q] += a[i] / pw[j], c1[j][q] += 1;
            }
            else {
                r0[j][q] += a[i] / pw[j], c0[j][q] += 1;
            }
        }
        ans += cur;
        ll x = a[i] * 2;
        while (x % 2 == 0) x /= 2;
        a2 += x;
    }
    ans += a2;
    printf("%lld\n", ans);
    return 0;
}

G

给定整数序列A=(A1,A2,…,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,,AN)B=(B1,B2,…,BN)B = (B_1, B_2, \ldots, B_N)B=(B1,B2,,BN),它们的长度均为NNN,以及整数序列X=(X1,X2,…,XK)X = (X_1, X_2, \ldots, X_K)X=(X1,X2,,XK)Y=(Y1,Y2,…,YK)Y = (Y_1, Y_2, \ldots, Y_K)Y=(Y1,Y2,,YK),它们的长度均为KKK

对于每个k=1,2,…,Kk = 1, 2, \ldots, Kk=1,2,,K,计算∑i=1Xk∑j=1Yk∣Ai−Bj∣\sum_{i=1}^{X_k} \sum_{j=1}^{Y_k} |A_i - B_j|i=1Xkj=1YkAiBj


莫队是一种巧妙的双指针遍历算法,使用分块在块中让一边的指针只遍历一遍,控制另一边指针的遍历次数。
这个题求[L,R]区间的函数值f(l,r)f(l,r)f(l,r),只要满足f(l,r)f(l,r)f(l,r)f(l±1,r)f(l\pm1,r)f(l±1,r)f(l,r)f(l,r)f(l,r)f(l,r±1)f(l,r\pm1)f(l,r±1)的转移是在常数系数就能使用莫队算法。
给定A1....AlA_1....A_lA1....Al,从BrB_rBrBr+1B_{r+1}Br+1,结果增加了∣Br+1−A1∣,∣Br+1−A2∣,....,∣Br+1−Al∣|B_{r+1}-A_1|,|B_{r+1}-A_2|,....,|B_{r+1}-A_l|Br+1A1,Br+1A2,....,Br+1Al
需要求的是其中AiA_iAiBr+1B_{r+1}Br+1小的那些数之和以及个数
因此离散化维护A的上述两个树状数组
反过来求AAA的转移需要维护B的树状数组。

离散化不要用unordered_map这种需要耗费时间的数据结构。

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

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;

const int N = 100020;
unordered_map<ll, int> h;
int hid;
int n;
ll a[N], b[N];
int ha[N], hb[N];

struct Node {
    int l, r, b, i;
};

ll sa, sb;
ll s;
int m;
ll ans[N];
vector<Node> q;

struct BIT {
    ll c[2 * N] = { 0 };
    void add(int x, ll v) {
        while (x <= hid) {
            c[x] += v;
            x += x & -x;
        }
    }
    ll get(int x) {
        ll ret = 0;
        while (x > 0) {
            ret += c[x];
            x -= x & -x;
        }
        return ret;
    }
};
BIT ca, cb, cnta, cntb;


int main() {
    //freopen("in.txt", "r", stdin);
    cin >> n;
    set<ll> st;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        st.insert(a[i]);
    }
    for (int i = 1; i <= n; ++i) {
        cin >> b[i];
        st.insert(b[i]);
    }
    for (auto x : st) {
        h[x] = ++hid;
    }
    for (int i = 1; i <= n; ++i) {
        ha[i] = h[a[i]];
        hb[i] = h[b[i]];
    }
    cin >> m;
    int sz = sqrt(n);
    for (int i = 0; i < m; ++i) {
        int l, r;
        cin >> l >> r;
        q.push_back({ l, r, l / sz, i });
    }
    sort(q.begin(), q.end(), [&](Node x, Node y) {
        if (x.b == y.b) {
            return x.b & 1 ? x.r > y.r : x.r < y.r;
        }
        else {
            return x.b < y.b;
        }
        });
    int l = 0, r = 0;
    for (auto [l0, r0, blk, i] : q) {
        while (l < l0) {
            ++l;
            ll x = a[l];
            ca.add(ha[l], x);
            cnta.add(ha[l], 1);
            ll s0 = cb.get(ha[l]), c0 = cntb.get(ha[l]);
            ll s1 = sb - s0, c1 = r - c0;
            s += x * c0 - s0 + s1 - x * c1;
            sa += a[l];
        }
        while (l > l0) {
            ll x = a[l];
            ca.add(ha[l], -x);
            cnta.add(ha[l], -1);
            ll s0 = cb.get(ha[l]), c0 = cntb.get(ha[l]);
            ll s1 = sb - s0, c1 = r - c0;
            s -= x * c0 - s0 + s1 - x * c1;
            sa -= a[l];
            --l;
        }
        while (r < r0) {
            ++r;
            ll x = b[r];
            cb.add(hb[r], x);
            cntb.add(hb[r], 1);
            ll s0 = ca.get(hb[r]), c0 = cnta.get(hb[r]);
            ll s1 = sa - s0, c1 = l - c0;
            s += x * c0 - s0 + s1 - x * c1;
            sb += b[r];
        }
        while (r > r0) {
            ll x = b[r];
            cb.add(hb[r], -x);
            cntb.add(hb[r], -1);
            ll s0 = ca.get(hb[r]), c0 = cnta.get(hb[r]);
            ll s1 = sa - s0, c1 = l - c0;
            s -= x * c0 - s0 + s1 - x * c1;
            sb -= b[r];
            --r;
        }
        ans[i] = s;
    }
    for (int i = 0; i < m; ++i) {
        printf("%lld\n", ans[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值