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=1N∑j=iNf(Ai+Aj)。
首先,这个和是一个累加,对于每个AiA_iAi来说,可以将前面的所有AjA_jAj记录下来
但是每个和都需要唯一表示成q∗2pq*2^pq∗2p的形式,因此需要对每个数除2p2^p2p的余数做记录,这样可以用hash来计算Ai+AjA_i+A_jAi+Aj是否能被2p2^p2p整除。
例如12=3∗2212=3*2^212=3∗22这里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=1Xk∑j=1Yk∣Ai−Bj∣。
莫队是一种巧妙的双指针遍历算法,使用分块在块中让一边的指针只遍历一遍,控制另一边指针的遍历次数。
这个题求[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_rBr到Br+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+1−A1∣,∣Br+1−A2∣,....,∣Br+1−Al∣
需要求的是其中AiA_iAi比Br+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;
}