世界征服者 题解
前言
版本最强:老贪酸菜。
题意简述
小 X 有 \(2\) 个士兵,对手小 Y 有 \(n\) 个士兵,血量分别为 \(h_1,\cdot,h_n\)。小 X 的第一个士兵每一回合可以对小 Y 的某一个士兵造成 \(a\) 点伤害,第二个士兵每一回合可以造成 \(b\) 点伤害。你可以认为小 Y 不会发动攻击,一个士兵血量小于等于零就被击杀了。求小 X 击杀小 Y 所有士兵的最少回合数。
\(n,a,b,h\leq10^3\),答案 \(\mathrm{ans}\leq5\times10^3\)。
题目分析
如果答案确定为 \(\mathrm{ans}\),那么相当于有 \(\mathrm{ans}\) 次 \(a\) 和 \(\mathrm{ans}\) 次 \(b\) 可用。所以不难想到设 \(f_{i,j}\) 表示击杀前 \(i\) 个士兵,用了 \(j\) 次 \(a\),使用 \(b\) 的最小次数。\(f_{0,0}=0\),\(\mathrm{ans}=\min\limits_{i=0}^{5\times10^3}\Big\{\max\{i,f_{n,i}\}\Big\}\)。
转移也是朴素的。设为了打第 \(i\) 个士兵,用了 \(k\) 个 \(a\),那么记 \(r=h_i-k\cdot a\),若 \(r>0\),则需要 \(\lceil r/b\rceil\) 次 \(b\)。
时间复杂度 \(\mathcal{O}(n\cdot\mathrm{ans}^2)\)。
由于 \(r\leq0\),后面那一坨都是 \(0\),于是可以用前缀最值优化。记 \(K=\lceil h_i/a\rceil\),\(g_{i,j}\) 是 \(f_{i,j}\) 关于 \(j\) 的前缀最小值,则:
时间复杂度 \(\mathcal{O}(n\cdot\mathrm{ans}\cdot\frac{\max h}{a})\)。
考虑优化掉 \(\frac{\max h}{a}\)。
不妨改变 \(r\) 的定义,令 \(r'=r+b-1\),则由于 \(r>0\),\(\lceil r/b\rceil=\lfloor r'/b\rfloor=\lfloor(h_i-k\cdot a+b-1)/b\rfloor\)。不妨改变枚举的 \(k\):
令 \(m=h_i-j\cdot a+b-1\),\(n=k\cdot a\),则等价于:
我们此时分离了关于 \(f_{i,j}\) 和 \(f_{i-1,k}\) 的部分。可以进一步考虑。
对于 \(m,n\ge 0\):
证明很简单。设 \(m=k_1b+r_1,n=k_2b+r_2\),其中 \(k_1,k_2\in\mathbb{Z}_{\ge0}\),\(r_1,r_2\in[0,b)\)。则 \(\lfloor m/b\rfloor=k_1\),\(\lfloor n/b\rfloor=k_2\)。
而 \(\lfloor(r_1+r_2)/b\rfloor\) 显然等价于 \([r_1+r_2\ge b]\),命题得证。
注意到需要有 \(m\ge0\) 结论才成立,而我们的 \(m=h_i-j\cdot a+b-1\) 可能小于零。此时不妨给 \(m\) 加上 \(t\) 个 \(b\),使 \(m\ge0\),最后再减去 \(t\)。
根据结论,于是有:
于是我们可以对艾佛森括号的取值分类讨论,需要维护 \(f_{i-1,k}+\lfloor n/b\rfloor\) 的区间最值。
但是还需要考虑 \(k\) 的范围。\(k\in[j-K+1,j]\),其中 \(K=\lceil h_i/a\rceil\)。我们可以对 \(h_i\) 从大到小排序,来保证 \(K\) 是单调不升的,此时 \(k\) 的区间是一个滑动窗口,滑动的时候需要用线段树维护即可。
时间复杂度 \(\mathcal{O}(n\cdot\mathrm{ans}\cdot\log b)\)。
题外话
正解需要大力卡常才能勉强过,被暴力 \(\mathcal{O}(n\cdot\mathrm{ans}\cdot\frac{\max h}{a})\) 艹飞了。
此外,lyd 使用了老贪酸菜做法,两只 \(\log\),经 cat 优化至一只 \(\log\),拿下最优解。什么?你说正确性?已经被 Hack 了。
原帖
1
#include <cstdio>
#include <iostream>
using namespace std;
int main() {
freopen("yzh", "w", stdout);
int t = 4;
cout << t << endl;
while (t--) {
int n = 1000, a = 1, b = 520;
cout << n << ' ' << a << ' ' << b << endl;
for (int i = 1; i <= n; ++i) {
cout << 1000 << " \n"[i == n];
}
}
return 0;
}
1996
1996
1996
1996
卡掉前缀最值优化的 \(\mathcal{O}(n\cdot\mathrm{ans}\cdot\frac{\max h}{a})\) 。/submission/357287、/submission/357329、/submission/357363。
这样除了老贪酸菜,考试代码全都超时了。
2
#include <cstdio>
#include <iostream>
#include <random>
using namespace std;
const unsigned SEED = 434933440;
mt19937 rng(SEED);
inline int rand(int l, int r) {
uniform_int_distribution<int> dist(l, r);
return dist(rng);
}
int main() {
freopen("yzh", "w", stdout);
int T = 1;
cout << T << endl;
while (T--) {
int n = rand(1, 1000);
int a = rand(1, 1000);
int b = rand(1, 1000);
cout << n << ' ' << a << ' ' << b << endl;
for (int i = 1; i <= n; ++i) {
cout << rand(1, 1000) << endl;
}
}
return 0;
}
1382
这样老贪酸菜也被卡掉了。
3
1
3 3 7
20
14
5
4
代码
可以继续优化。\(f\) 本身就是单调不升的,不需要记录前缀最值。滚动数组以减少 cache miss。
#include <cstdio>
#include <queue>
#include <algorithm>
#include <set>
using namespace std;
const int MAX = 1 << 26;
char buf[MAX], *inp = buf;
template <typename T>
inline void read(T &x) {
x = 0; char ch = *inp++;
for (; ch < 48; ch = *inp++);
for (; ch >= 48; ch = *inp++) x = (x << 3) + (x << 1) + (ch ^ 48);
}
const int N = 1e3 + 10, M = 5e3 + 10;
const int inf = 0x3f3f3f3f;
inline int min(int a, int b) { return a < b ? a : b; }
int n, a, b;
int h[N];
#define ls (i << 1)
#define rs (i << 1 | 1)
int mi[1 << 11];
// struct {
// priority_queue<int, vector<int>, greater<int>> Q, D;
// inline void insert(int x) { Q.emplace(x); }
// inline void erase(int x) { D.emplace(x); }
// inline bool empty() {
// while (!D.empty() && D.top() == Q.top()) Q.pop(), D.pop();
// return Q.empty();
// }
// inline int top() {
// while (!D.empty() && D.top() == Q.top()) Q.pop(), D.pop();
// return Q.top();
// }
// inline void clear() {
// decltype(Q)().swap(Q);
// decltype(D)().swap(D);
// }
// } st[N];
struct {
multiset<int> st;
inline void insert(int x) { st.emplace(x); }
inline void erase(int x) { st.erase(st.find(x)); }
inline bool empty() { return st.empty(); }
inline int top() { return *st.begin(); }
inline void clear() { st.clear(); }
} st[N];
int RN, B;
inline void build(int b) {
B = b;
RN = 1 << (__lg(b + 5) + 1);
for (int i = 1; i <= b; ++i) st[i].clear(), mi[RN + i] = inf;
for (int i = RN; i; --i) mi[i] = inf;
}
inline void upd(int i, int v) {
st[++i].insert(v), i += RN;
if (v >= mi[i]) return;
mi[i] = v;
for (i >>= 1; i; i >>= 1) {
v = min(mi[ls], mi[rs]);
if (v == mi[i]) break;
mi[i] = v;
}
}
inline void del(int i, int v) {
st[++i].erase(v);
v = st[i].empty() ? inf : st[i].top();
i += RN;
mi[i] = v;
for (i >>= 1; i; i >>= 1) {
v = min(mi[ls], mi[rs]);
if (v == mi[i]) break;
mi[i] = v;
}
}
inline int qryP(int i) {
++i;
int r = inf, j = RN;
for (i += RN + 1; i ^ j ^ 1; i >>= 1, j >>= 1) {
if (~j & 1) r = min(r, mi[j ^ 1]);
if (i & 1) r = min(r, mi[i ^ 1]);
}
return r;
}
inline int qryS(int i) {
++i;
if (i > B) return inf;
int r = inf, j = RN + B + 1;
for (i += RN - 1; i ^ j ^ 1; i >>= 1, j >>= 1) {
if (~i & 1) r = min(r, mi[i ^ 1]);
if (j & 1) r = min(r, mi[j ^ 1]);
}
return r;
}
#undef ls
#undef rs
int f[N][M], g[N][M], ab[M], ab2[M];
void solve() {
read(n), read(a), read(b);
for (int i = 1; i <= n; ++i) {
read(h[i]);
}
sort(h + 1, h + n + 1, [] (int x, int y) {
int ox = (x - 1) / a + 1;
int oy = (y - 1) / a + 1;
return ox == oy ? x > y : ox < oy;
});
f[0][0] = 0;
constexpr int x = 5000;
for (int i = 0; i <= x; ++i) {
ab[i] = i * a % b;
ab2[i] = i * a / b;
}
int sm = 0, lstUP = 0;
for (int i = 1; i <= n; ++i) {
build(b);
sm += (h[i] - 1) / a + 1;
int UP = min(x, sm);
for (int j = 0, lst = 0; j <= UP; ++j) {
if (j <= lstUP) {
int v = f[i - 1][j] + ab2[j];
upd(ab[j], v);
}
int O = (h[i] - 1) / a + 1;
f[i][j] = inf;
if (0 <= j - O && j - O <= lstUP)
f[i][j] = g[i - 1][j - O];
int o = j - min(O - 1, j) - 1;
if (o >= lst && o <= lstUP) {
int v = f[i - 1][o] + ab2[o];
del(ab[o], v);
lst = o + 1;
}
int m = h[i] - j * a + b - 1, ad = 0;
if (m < 0) {
ad = (-m - 1) / b + 1;
m += ad * b;
}
int p = b - m % b;
f[i][j] = min(f[i][j], -ad + m / b + qryP(p - 1));
f[i][j] = min(f[i][j], -ad + m / b + qryS(p) + 1);
g[i][j] = j ? min(g[i][j - 1], f[i][j]) : f[i][j];
}
lstUP = UP;
}
int mi = inf, ans = -1;
for (int i = 0; i <= lstUP; ++i) {
mi = min(mi, f[n][i]);
if (mi <= i) {
ans = i;
break;
}
}
printf("%d\n", ans);
}
int main() {
fread(buf, 1, MAX, stdin);
int t;
read(t);
while (t--) {
solve();
}
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/19015003。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号