C. Striped Horse
观察选定的正整数 \(x\) 后,满足条件的下标集合只与 \((i+x) \bmod 2W\) 是否落在区间 \([0,W−1]\) 有关。也就是说,把下标按模 \(2W\) 分组,所选的格子就是这 \(2W\) 个同余类中某个长度为 \(W\) 的连续区间(环上的连续区间)。
因此先把每个残类的代价累加成数组 \(d\):令
对于某个 \(x\),总费用就是在环长 \(2W\) 的数组 \(d\) 上取一个长度为 \(W\) 的连续窗口的和。
问题就变成在环上找长度为 \(W\) 的连续子数组的最小和。常见做法是破环成链,然后用滑动窗口求长度为 \(W\) 的最小窗口和。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
void solve() {
int n, w;
cin >> n >> w;
int w2 = w*2;
vector<int> c(n);
rep(i, n) cin >> c[i];
vector<ll> d(w2);
rep(i, n) d[i%w2] += c[i];
rep(i, w2) d.push_back(d[i]);
ll sum = 0;
rep(i, w) sum += d[i];
ll ans = sum;
rep(i, w2) {
sum -= d[i];
sum += d[i+w];
ans = min(ans, sum);
}
cout << ans << '\n';
}
int main() {
int t;
cin >> t;
while (t--) solve();
return 0;
}
D. Forbidden List 2
二分套二分
先对数组 \(A\) 排序。记 \(i(r)=\#\{a_j \leqslant r\}\),用 \(\text{upper_bound}\) 得到。
定义函数
这表示在区间 \([1,r]\) 中不在数组里的整数个数。\(f(r)\) 随 \(r\) 单调不降。
对一次查询 \((X,Y)\),要找最小的 \(r\) 使得区间 \([X,r]\) 内缺失的整数数目 \(\geqslant Y\)。区间内的缺失数为
因此用二分查找最小 \(r\) 满足 \(f(r)−f(X−1) \geqslant Y\) 。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n, q;
cin >> n >> q;
vector<int> a(n);
rep(i, n) cin >> a[i];
sort(a.begin(), a.end());
auto f = [&](int r) {
int i = upper_bound(a.begin(), a.end(), r) - a.begin();
return r - i;
};
rep(qi, q) {
int x, y;
cin >> x >> y;
int wa = x-1, ac = x+y+n+1;
while (wa+1 < ac) {
int wj = wa + (ac-wa)/2;
if (f(wj)-f(x-1) >= y) ac = wj; else wa = wj;
}
cout << ac << '\n';
}
return 0;
}
E. Cookies
把题目转成“把 \(K\) 个相同球放入 \(N\) 个桶,每种放 \(\sum d_i=K\)”。每个状态由数组 \(d=(d_0,…,d_{n−1})\) 表示,代价为 \(\sum a_i \times d_i\) 。若把 \(a\) 按降序排序,初始最大状态是 \(d=(K,0,…,0)\) 。
目标是按代价从大到小枚举前 \(X\) 个不同的 \(d\) 的代价值。
用大根堆在状态图上枚举代价从大到小的状态,且从每个被弹出的状态只生成有限的“邻居”状态,这些邻居就是可能成为下一个候选的分配方式。(可以发现是树结构)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n, k, x;
cin >> n >> k >> x;
vector<int> a(n);
rep(i, n) cin >> a[i];
sort(a.begin(), a.end(), greater<>());
vector<int> d(n);
d[0] = k;
priority_queue<pair<ll, vector<int>>> q;
auto push = [&](const vector<int>& d) {
ll s = 0;
rep(i, n) s += (ll)a[i]*d[i];
q.emplace(s, d);
};
push(d);
while (x--) {
auto [s, d] = q.top(); q.pop();
cout << s << '\n';
int i = n-1;
while (d[i] == 0) i--;
if (d.back() == 0) {
d[i]--; d[i+1]++;
push(d);
d[i]++; d[i+1]--;
}
if (i >= 1 and d[i-1] > 0) {
d[i-1]--; d[i]++;
push(d);
}
}
return 0;
}
F. Egoism
将总心情记做 \(S = \sum A_i\) 。对于任意排列,总满足度可写成
由于 \(B∈\{1,2\}\),第二项就是“所有处于某个有 \(B=2\) 的马后面的马的心情之和”。如果当前有 \(B=2\) 的马,则我们最多能让 \(t\) 个不同的马作为“后继”从而各自多得到 \(+A\)(注意最多只能有 \(N−1\) 个后继),因此理想情况下额外能加上的最大值就是对所有 \(A_i\) 取前 \(k=\min(t,N−1)\) 大的和。
需要实现以下需求的数据结构:
- 数的添加/删除
- 最大的 \(k\) 个数的和
- 对 \(k\) \(+1 / -1\)
可以用对顶堆来实现
如果有 \(k\) 个 \(2\),基本上把按降序的前 \(k\) 个设为 \(2\);但若按降序的前 \(k\) 个都是 \(2\) 会形成闭环,所以要舍弃一个(让其中一个不为 \(2\))。
因此,还需要额外开两个 \(\text{multiset}\) 来分别维护 \(B=1\) 的 \(A_i\) 以及 \(B=2\) 的 \(A_i\) 。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using S = multiset<int>;
struct Divset {
S ls, rs;
int k; ll rsum;
Divset(): k(0), rsum(0) {}
void fix() {
while (rs.size() < k) {
int x = *ls.rbegin();
rsum += x;
rs.insert(x);
ls.erase(ls.find(x));
}
while (rs.size() > k) {
rsum -= *rs.begin();
ls.insert(*rs.begin());
rs.erase(rs.begin());
}
}
void add(int x) {
rs.insert(x); rsum += x;
fix();
}
void del(int x) {
if (ls.find(x) != ls.end()) ls.erase(ls.find(x));
else rs.erase(rs.find(x)), rsum -= x;
fix();
}
void inc() {
k++; fix();
}
void dec() {
k--; fix();
}
};
int main() {
int n, q;
cin >> n >> q;
ll sum = 0, sum2 = 0;
S s1, s2;
Divset ds;
auto add = [&](int a, int b) {
ds.add(a);
sum += a;
if (b == 2) ds.inc(), sum2 += a;
if (b == 1) s1.insert(a); else s2.insert(a);
};
auto del = [&](int a, int b) {
if (b == 1) s1.erase(s1.find(a)); else s2.erase(s2.find(a));
if (b == 2) ds.dec(), sum2 -= a;
ds.del(a);
sum -= a;
};
vector<int> a(n), b(n);
rep(i, n) {
cin >> a[i] >> b[i];
add(a[i], b[i]);
}
rep(qi, q) {
int i, x, y;
cin >> i >> x >> y;
--i;
del(a[i], b[i]);
a[i] = x; b[i] = y;
add(a[i], b[i]);
ll ans = sum + ds.rsum;
if (sum2 != 0 and ds.rsum == sum2) {
ans -= *s2.begin();
if (sum2 < sum) ans += *s1.rbegin();
}
cout << ans << '\n';
}
return 0;
}
G. Haunted House
先沿着上升方向建 \(\text{DAG}\),记录每个点的 \(\text{top2}\) 并做 \(\text{dp}\),随后考虑允许“返回一次”的情况,对每个点维护 \(\text{top2}\) 继续 \(\text{dp}\) 。
浙公网安备 33010602011771号