24 Hongkong B and 2023 ICPC Shenyang
24 Hongkong B and 2023 ICPC Shenyang
24 Hongkong B
我们能造成的伤害范围比较小,考虑从这一点入手。如果每次都造成 1 点伤害,则 \(max\{a_i + b_i\}\) 次后就能击败所有敌人,最后造成 \(max\{a_i + b_i\}\) 点伤害,这是造成总伤害的最小值。在破甲和击杀时,对于非 1 的伤害,都有可能溢出,两次溢出最多溢出 \(2k\) 点伤害,考虑破甲的溢出,当造成的总伤害超过 \(max\{a_i + b_i\} + k\) 时,必然会击败所有对手,但是再考虑击败时的溢出,我们最后造成的总伤害可能会达到 \(max\{a_i + b_i\} + 2k\)。也就是说,我们最后造成的总伤害的范围是小的。
这启事我们可以枚举最后造成的总伤害算最小代价和方案数。具体得,设我们确定造成得总伤害为 \(s\),若当前已造成了 \(x\) 点伤害,下一步我想造成 \(y\) 点伤害,则会对护盾值 \(\in (x, x + y]\) 范围内的敌人造成破甲,此时若想后续伤害 \(s - x - y\) 仍有可能击败所有敌人,就需要 \(s - x - y\) 大于护盾值 \(\in (x, x + y]\) 的敌人的最高血量。于是就可以根据这个约束来 dp
int n, m, k;
int a[N + 5], b[N + 5];
// hp[i] 护盾值为 i 的敌人的最高血量
int hp[S];
i64 cost[K + 5];
// 最小代价、方案数
i64 f[S + 5], g[S + 5];
void add(i64 &a, i64 b) {
a += b;
if (a >= Mod) {
a -= Mod;
}
}
std::array<i64, 2> get(int s) {
for (int i = 0; i <= s; ++i) {
f[i] = Inf;
g[i] = 0ll;
}
f[0] = 0ll;
g[0] = 1ll;
for (int i = 0; i <= s; ++i) {
int mx = 0;
for (int j = 1; j <= k && i + j <= s; ++j) {
mx = std::max(mx, hp[i + j]);
// 剩余的伤害要大于血量
if (s - (i + j) < mx) {
break;
}
i64 val = f[i] + cost[j];
if (val == f[i + j]) {
add(g[i + j], g[i]);
}
if (val < f[i + j]) {
f[i + j] = val;
g[i + j] = g[i];
}
}
}
return { f[s], g[s] };
}
void solve() {
std::cin >> n >> m;
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
for (int i = 0; i < n; ++i) {
std::cin >> b[i];
}
std::cin >> k;
for (int i = 1; i <= k; ++i) {
std::cin >> cost[i];
}
int mx = 0;
for (int i = 0; i < n; ++i) {
mx = std::max(a[i] + b[i], mx);
}
for (int i = 1; i <= k + k + mx; ++i) {
hp[i] = 0;
}
for (int i = 0; i < n; ++i) {
hp[b[i]] = std::max(hp[b[i]], a[i]);
}
i64 mn = Inf;
i64 cnt = 0;
// 枚举总伤害
for (int i = mx; i <= k + k + mx; ++i) {
auto [fs, gs] = get(i);
if (fs == mn) {
add(cnt, gs);
}
if (fs < mn) {
mn = fs;
cnt = gs;
}
}
std::cout << mn << ' ' << cnt << '\n';
}
23 Senyang
E
数据范围非常的小啊,所有的状态,加上状态之间可能的转移也就 \(n^4\),所以直接 bfs 爆搜
void solve() {
int x = 0, y = 0, p = 0, q = 0;
std::cin >> x >> y >> p >> q;
int ans = Inf;
std::queue<std::array<int, 4>> que;
que.push({ x, y, 0, 0 });
vis[x][y][0] = true;
while (not que.empty()) {
auto [ux, uy, side, d] = que.front();
que.pop();
if (ux == 0 && side == 1) {
ans = std::min(ans, d);
}
// 在家一侧
if (side == 1) {
for (int dx = 0; dx + ux <= x; ++dx) {
for (int dy = 0; dy + uy <= y; ++dy) {
if (dx + dy > p) {
break;
}
// 家对岸
int vx = ux + dx;
int vy = uy + dy;
// 家
int hx = x - vx;
int hy = y - vy;
if ((hx != 0 && hy > hx + q) || vis[vx][vy][0]) {
continue;
}
que.push({ vx, vy, 0, d + 1 });
vis[vx][vy][0] = true;
}
}
}
// 在对岸
else {
for (int dx = 0; dx <= ux; ++dx) {
for (int dy = 0; dy <= uy; ++dy) {
if (dx + dy > p) {
break;
}
// 家对岸
int vx = ux - dx;
int vy = uy - dy;
// 家
int hx = x - vx;
int hy = y - vy;
if ((vx != 0 && vy > vx + q) || vis[vx][vy][1]) {
continue;
}
que.push({ vx, vy, 1, d + 1 });
vis[vx][vy][1] = true;
}
}
}
}
std::cout << (ans == Inf ? -1 : ans) << '\n';
}
J
考虑什么样的边不能操作,可以发现与叶子相连的边不能选,对与叶子相连的边镜像进行一次操作相当于交换了叶子和他的父亲,树的形态仍保持不变。其次能够操作的边最多只能操作一次,因为操作一次之后,所选的那条边就与叶子相邻了。所以只用看可操作边的奇偶就好了。
K
能够更新的最多次数是好求的:把所有的正数调整到非正数前面,操作次数就是正数的数量。对于最少次数,只需要把所有非正数放在正数前面,并将正数排成升序,这样能让分数大于 0 后,剩下的正数尽可能少,也就是更新的次数尽可能少,假设正数的数量是 \(x\),对正数升序排序后,第一个让分数大与 0 的正数是 \(k\),则答案就是 \(x - (x - k) = k\),也就是找到 \(k\) 就行了。线段树上二分易解:
struct Info {
int num;
i64 sum;
Info *l, *r;
Info() : num(0), sum(0), l(nullptr), r(nullptr) {}
~Info() {
if (!l) {
delete l;
l = nullptr;
}
if (!r) {
delete r;
r = nullptr;
}
}
} *root = nullptr;
int a[N + 5];
void add(Info *&cur, int l, int r, int pos, int val) {
if (!cur) {
cur = new Info();
}
cur->num += val;
cur->sum += 1ll * pos * val;
if (l == r) {
return;
}
int m = l + r >> 1;
if (pos <= m) {
add(cur->l, l, m, pos, val);
}
else {
add(cur->r, m + 1, r, pos, val);
}
}
int upper_bound(Info *&cur, int l, int r, i64 val) {
if (!cur) {
return 0;
}
if (l == r) {
return val / l;
}
int lnum = 0;
i64 lsum = 0ll, rsum = 0ll;
if (cur->l) {
lsum = cur->l->sum;
lnum = cur->l->num;
}
if (cur->r) {
rsum = cur->r->sum;
}
int m = l + r >> 1;
if (lsum > val) {
return upper_bound(cur->l, l, m, val);
}
else {
return lnum + upper_bound(cur->r, m + 1, r, val - lsum);
}
}
void solve() {
int n = 0, q = 0;
std::cin >> n >> q;
i64 neg = 0;
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
if (a[i] > 0) {
add(root, 1, Inf, a[i], 1);
}
else {
neg -= a[i];
}
}
while (q--) {
int idx = 0, val = 0;
std::cin >> idx >> val;
if (a[idx] > 0) {
add(root, 1, Inf, a[idx], -1);
}
else {
neg += a[idx];
}
a[idx] = val;
if (a[idx] > 0) {
add(root, 1, Inf, a[idx], 1);
}
else {
neg -= a[idx];
}
std::cout << upper_bound(root, 1, Inf, neg) + 1 << '\n';
}
}
浙公网安备 33010602011771号