Luogu P11014 「ALFR Round 4」D 罪人的终幕 题解 [ 紫 ] [ 李超线段树 ] [ 线性筛 ] [ 数学 ]

罪人的终幕:比较一眼的李超线段树推式子题。

首先来推一推答案的式子,因为 \(k\) 是一个常数所以显然可以先移出 \(\max\)

\[m_i = \max\{\dfrac{m_j}{a(\operatorname{lcm}(w_i,w_j)) + a(\gcd(w_i,w_j))}\} + k \]

因为这个式子涉及 \(\gcd,\operatorname{lcm}\),所以可以尝试从唯一分解定理的角度考虑一下,发现 \(a(\operatorname{lcm}(w_i,w_j))\) 显然可以容斥算一下,即 \(a(\operatorname{lcm}(w_i,w_j))=a(w_i) + a(w_j)-a(\gcd(w_i,w_j))\)。因为 \(\gcd(w_i,w_j)\) 包含了 \(w_i,w_j\) 共有的质因子,也就消去了重复的贡献。

因此,原式子变为了:

\[m_i = \max\{\dfrac{m_j}{a(w_i)+a(w_j)}\} + k \]

固定 \(i\) 后,发现唯一的变量 \(j\) 在分母,不是很好搞,因此我们可以取倒数,把变量搞到分子上,同时 \(\max\) 也会变成 \(\min\)

\[m_i = \dfrac{1}{\min\{\dfrac{a(w_i)+a(w_j)}{m_j}\}} + k \]

因此我们只需要维护所有 \(j\) 中的 \(\min\{\dfrac{a(w_i)+a(w_j)}{m_j}\}\) 即可。

\(i,j\) 项分离,发现能写成一个一次函数的形式:

\[res = \min\{\dfrac{a(w_i)}{m_j} + \dfrac{a(w_j)}{m_j}\} = \min\{\dfrac{1}{m_j}a(w_i) + \dfrac{a(w_j)}{m_j}\} \]

其中 \(y=res, k=\dfrac{1}{m_j}, b = \dfrac{a(w_j)}{m_j},x = a(w_i)\)

显然可以直接李超树维护每个 \(x\) 的最小值,因为是全局插入所以时间复杂度 \(O(n\log n)\)

而求 \(a(i)\) 可以直接线性筛,根据线性筛每个合数由最小质因子筛掉的性质递推一下即可。这一部分时间复杂度 \(O(n)\)

总体时间复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc (p << 1)
#define rc ((p << 1) | 1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 200005, MXV = 182376;
const ldb eps = 1e-18;
ll n, m, w[N], cnt;
ldb ans[N], res;
int prm[N], vis[N], prmcnt, mnprm[N], a[N];
void init()
{
    for(int i = 2; i <= MXV; i++)
    {
        if(vis[i] == 0) prm[++prmcnt] = i, mnprm[i] = i, a[i] = i;
        for(int j = 1; j <= prmcnt && i * prm[j] <= MXV; j++)
        {
            vis[prm[j] * i] = 1;
            mnprm[prm[j] * i] = prm[j];
            if(i % prm[j] == 0)
            {
                a[prm[j] * i] = a[i];
                break;
            }
            a[prm[j] * i] = a[i] + prm[j];
        }
    }
}
struct Line{
    ldb k, b;
}line[N];
ldb gety(int id, ldb x)
{
    return (line[id].k * x + line[id].b);
}
int cmp(ldb a, ldb b)
{
    if(a - b > eps) return 1;
    if(b - a > eps) return -1;
    return 0;
}
struct Node{
    int l, r, id;
};
struct Segtree{
    Node tr[4 * N];
    void build(int p, int ln, int rn)
    {
        tr[p] = {ln, rn, 1};
        if(ln ==  rn) return;
        int mid = (ln + rn) >> 1;
        build(lc, ln, mid);
        build(rc, mid + 1, rn);
    }
    void update(int p, int id)
    {
        int mid = (tr[p].l + tr[p].r) >> 1;
        if(cmp(gety(id, mid), gety(tr[p].id, mid)) == -1) swap(id, tr[p].id);
        if(cmp(gety(id, tr[p].l), gety(tr[p].id, tr[p].l)) == -1) update(lc, id);
        if(cmp(gety(id, tr[p].r), gety(tr[p].id, tr[p].r)) == -1) update(rc, id);
    }
    ldb query(int p, int pos)
    {
        if(tr[p].l == pos && tr[p].r == pos) return gety(tr[p].id, pos);
        int mid = (tr[p].l + tr[p].r) >> 1;
        ldb mn = gety(tr[p].id, pos);
        if(pos <= mid) return min(mn, query(lc, pos));
        else return min(mn, query(rc, pos));
    }
}tr1;
int main()
{
    init();
    scanf("%lld%Lf%lld", &n, &ans[1], &m);
    for(int i = 1; i <= n; i++) scanf("%lld", &w[i]);
    line[++cnt] = {ldb(1) / ans[1], ldb(a[w[1]]) / ans[1]};
    tr1.build(1, 1, MXV);
    res += ans[1];
    for(int i = 2; i <= n; i++)
    {
        ans[i] = ldb(1) / tr1.query(1, a[w[i]]) + m;
        line[++cnt] = {ldb(1) / ans[i], ldb(a[w[i]]) / ans[i]};
        tr1.update(1, cnt);
        res += ans[i];
    }
    printf("%.18Lf", res);
    return 0;
}
posted @ 2025-08-16 17:18  KS_Fszha  阅读(6)  评论(0)    收藏  举报