世界征服者 题解

前言

版本最强:老贪酸菜。

题意简述

小 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\)

\[f_{i,j}=\min_{k=0}^j\Bigg\{ f_{i-1,j-k}+\max\Bigg\{0,\left\lceil\frac{h_i-k\cdot a}{b} \right\rceil\Bigg\}\Bigg\} \]

时间复杂度 \(\mathcal{O}(n\cdot\mathrm{ans}^2)\)

由于 \(r\leq0\),后面那一坨都是 \(0\),于是可以用前缀最值优化。记 \(K=\lceil h_i/a\rceil\)\(g_{i,j}\)\(f_{i,j}\) 关于 \(j\) 的前缀最小值,则:

\[f_{i,j}=\min\Big\{g_{i-1,j-K},\min_{k=0}^{K-1} f_{i-1,j-k}+\lceil r/b\rceil\Big\} \]

时间复杂度 \(\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\)

\[\min_{k=j-K+1}^j f_{i-1,k}+\lfloor(h_i-(j-k)a+b-1)/b\rfloor \]

\(m=h_i-j\cdot a+b-1\)\(n=k\cdot a\),则等价于:

\[\min_{k=j-K+1}^j f_{i-1,k}+\lfloor(m+n)/b\rfloor \]

我们此时分离了关于 \(f_{i,j}\)\(f_{i-1,k}\) 的部分。可以进一步考虑。

对于 \(m,n\ge 0\)

\[\lfloor(m+n)/b\rfloor=\lfloor m/b\rfloor+\lfloor n/b\rfloor+[(m\bmod b)+(n\bmod b)\ge b] \]

证明很简单。设 \(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(m+n)/b\rfloor=k_1+k_2+\lfloor(r_1+r_2)/b\rfloor \]

\(\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\)

根据结论,于是有:

\[\lfloor m/b\rfloor+f_{i-1,k}+\lfloor n/b\rfloor+[(n\bmod b)\ge b-(m\bmod b)] \]

于是我们可以对艾佛森括号的取值分类讨论,需要维护 \(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;
}
posted @ 2025-07-31 18:18  XuYueming  阅读(24)  评论(0)    收藏  举报