[SNCPC2024] 2024 年陕西省大学生程序设计 J题猜质数II 题解

题目链接:CF 或者 洛谷

前提说明

其实在上个月就见到这题了,当时很想做这题,结果找不到做题链接,也不知道出处,原来是陕西省赛的捧杯题

个人评价觉得是一道很不错的题,难度适中。

讲解

其实题解写的挺不错的,比很多比赛的题解写的详细许多了。这里站在我的角度分析这题吧:

先观察要求的式子:

\[score(x,l,r)=\sum_{i=l}^r{f(x,a_i)} \]

其中

\[f(x,y)=\left\{\begin{array}{rcl}u-y, & x=1 \\ u, & 1<x\le y,\ \gcd(x,y)=1 \\ -x\cdot y, & x\neq 1,\ \gcd(x,y)=x \\ 0, & \text{otherwise} \end{array}\right. \]

\(u\) 是题目给定查询的。看到这一坨就很麻烦,我们看看询问啥东西:

\[\sum_{x=1}^{1e6}score(x,l,r)=\sum_{x=1}^{1e6}\sum_{i=l}^r{f(x,a_i)} \]

那看来肯定是需要变形了,我们重点分析一下:

\[\sum_{x=1}^{1e6}\sum_{i=l}^r{f(x,a_i)}=\sum_{i=l}^r\sum_{x=1}^{1e6}{f(x,a_i)} \]

\[\sum_{x=1}^{1e6}{f(x,a_i)}=? \]

那么我们得重点分析一下这里面的每种情况的贡献:

  1. 首先观察到 \(0\) 的情况,当 \(x>y\) 时,上述三点没有任何一点满足,所以我们只需要考虑 \(1\sim y\) 的数的贡献,直接分别考虑三种情况的贡献加起来就行了。

  2. 先考虑第二种情况,即 \(1< x\le y\) 中与 \(y\) 互质的数的个数,这个为欧拉函数的定义,即为 \(cnt=eulr[y]-1\),因为这里不包含 \(1\)。所以贡献为:\(cnt \times u\)。主要这里面 \(x>1\),所以少了一个 \(u\),为 \(x=1\) 的贡献。

  3. 考虑第三种情况,\(\gcd(x,y)=x\),说明 \(x\)\(y\) 的因子,总贡献为:\(-X \times y\),其中 \(X\) 为所有符合题意的 \(x\) 的和,即为 \(y\) 的因子和。但要注意一下,这里 \(x \neq 1\),所以因子和里面要少个 \(-1 \times y\)

  4. 考虑情况 \(1\),我们发现二三情况总共少了个 \(u-y\) 关于 \(x=1\) 的贡献,而 \(1\) 情况恰好补上了,那么我们可以这样处理 \(2\)\(3\) 情况全部考虑 \(x=1\) 的贡献,然后不考虑 情况 \(1\),这样贡献也正确了。

这样一来由上述分析可知道:

我们记:\(eulr[x]\) 表示 \(x\) 的欧拉函数值,\(pw[x]\) 为关于 \(x\) 的所有因数和,包括自身。

那么:

\[\sum_{x=1}^{1e6}{f(x,a_i)}=u \times eulr[a_i]-a_i \times pw[a_i] \]

而这里面的 \(eulr\)\(pw\) 都可以用线性筛线性预处理:\(O(n)\),当然了,如果不会 \(pw\) 的线性预处理,也可以使用调和级数:\(O(n\ln{n})\) 预处理,即枚举每个数的所有倍数加入这个数的贡献。

我们现在回到原式:

发现是 \(l\) 是固定的,\(r\) 是需要枚举的,枚举出哪个是最优的 \(r\),而无论如何,我们都涉及到了一个 \([l,r]\) 上的答案查询,那么我们显然不可能暴力进行 \([l,r]\) 上的答案查询,我们用传统的套路,考虑这个问题 可不可以差分\(ans(l,r)=ans(r)-ans(l-1)\),这题里是显而易见可以的,因为每一项都是独立的,并不涉及到若干项之间还有什么 \(\max\)\(\gcd\) 之类的:

即设 \(val[i]=u \times eulr[a_i]-a_i \times pw[a_i]\),其中规定 \(val[0]=0\)

\[ans(l,r)=val[l]+val[l+1]+val[l+2]....val[r] \]

每一项 \(val[i]\) 都是独立的,显然可以有:

\[ans(r)=val[0]+val[1]+val[2]+val[3]+....val[r] \]

\[ans(l,r)=ans(r)-ans(l-1),问题即有可差性,为可差分问题 \]

我们容易知道 \(ans(l-1)\) 为定值,因为 \(l\) 是题目给定的,而 \(r\) 是我们需要寻找的。并且 \(val[i]\) 经过预处理前缀和以后,我们是可以直接 \(O(1)\) 查询的。

\(ans(l-1)=pre[l-1]=val[0]+val[1]+val[2]....+val[l]\)

现在我们来到了寻找 \(r\) 的阶段,显然不可能枚举每个 \(r\) 然后去求最佳,肯定要用什么 \(\log\) 之类的查询方式。

我们观察

\[ans(r)=pre[r]=\sum_{i=0}^{r} (u \times eulr[a_i]-a_i \times pw[a_i]) \]

\[=u \times \sum_{i=0}^{r} eulr[a_i]- \sum_{i=0}^{r} a_i \times pw[a_i] \]

\[k=\sum_{i=0}^{r} eulr[a_i],b=-\sum_{i=0}^{r} a_i \times pw[a_i] \]

那么显然每个 \(r\) 都是一个 \(ans(r)=k_r \times u +b_r\),一个一次函数。

那么其实思路就很简单了,一堆 带限制的 一次函数最大值查询。

关于斜率最值查询有很多工具:半平面交、凸壳二分/三分、李超线段树...

本题就讲讲个人比较喜欢用的两种,凸壳和李超树怎么分析和书写吧,半平面交只会一些板子题,等后续更深了再补。

凸包做法

关于凸壳求最大值:

  1. 构建下凸壳。

  2. 凸壳上二分。

先考虑第一个问题,第一个问题我们需要将 \((k,b)\) 进行排序,然后再做单调队列。我们观察询问,\(r_i \ge l_i\),对于每个询问来说,它们的 \((k,b)\) 集合是不相同的,不可能每次都重建凸壳,那么这时候我们需要考虑一下如何让凸壳不重建:

观察:

\[k=\sum_{i=0}^{r} eulr[a_i],b=-\sum_{i=0}^{r} a_i \times pw[a_i] \]

这两个东西,当 \(r\) 变大,\(eulr[a_i]>0\),显然 \(k\) 单调递增。同理 \(a_i \times pw[a_i]>0\),所以 \(b\) 是单调递减的,当然我们的凸壳排序主要是跟 \(k\) 有关,那么我们观察到有:

\[k\ 关于\ r\ 是单调递增的 \]

这意味着我们如果从左往右遍历,只需要在凸壳最后加入新的 \((k,b)\),从右往左只需要在凸壳前面增加 \((k,b)\),加入时同时跑单调队列均摊是 \(O(n)\) 的。

现在我们考虑所有的查询 \(l\) 有序,我们注意到 \([l,n]\) 范围的集合是我们需要的,最好的就是每次比上一次多增加新的 \((k,b)\),从左往右的话,我们的 \(l\) 增大,集合是变小的,不符合要求,当然可以用可撤销凸包实现,但没啥必要,所以我们考虑 \(l\) 从右往左,这样 \(l\) 变小,可选集合增大,每次只需要增加新进的 \((k,b)\) 在凸包前部,然后跑单调队列维护下凸壳。查询就简单了,基本的凸包上二分或者三分最值即可。

最后我们来分析下数据范围:

首先 \(pre[r]\) 是前缀和,最坏的话假如取个 \(a_i\) 为范围内的最大质数,那么前缀和显然是 \(1e12\) 级别。注意到 \(u\)\(2e7\) 左右,虽然还有减去后面前缀和的操作,但还是可能会爆 \(long long\),建议使用 \(int128\),尤其是叉积。最后注意还有个相同最大值取最小 \(r\),这个时候只需要在二分时候注意严格比较即可。

PS: 本题时限很大,所以本人一般只有最优解时才考虑用快读卡常,其余时刻尽量以比较简略的读入,方便读者阅读,读者可以在需要卡常的时候自行使用快读。

凸包二分+因数和调和级数预处理
#include <bits/stdc++.h>

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

// #define isPbdsFile

#ifdef isPbdsFile

#include <bits/extc++.h>

#else

#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>

#endif

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

template <typename T>
int disc(T* a, int n)
{
    return unique(a + 1, a + n + 1) - (a + 1);
}

template <typename T>
T lowBit(T x)
{
    return x & -x;
}

template <typename T>
T Rand(T l, T r)
{
    static mt19937 Rand(time(nullptr));
    uniform_int_distribution<T> dis(l, r);
    return dis(Rand);
}

template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
    return (a % b + b) % b;
}

template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
    a %= c;
    T1 ans = 1;
    for (; b; b >>= 1, (a *= a) %= c) if (b & 1) (ans *= a) %= c;
    return modt(ans, c);
}

template <typename T>
void read(T& x)
{
    x = 0;
    T sign = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-') sign = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    x *= sign;
}

template <typename T, typename... U>
void read(T& x, U&... y)
{
    read(x);
    read(y...);
}

template <typename T>
void write(T x)
{
    if (typeid(x) == typeid(char)) return;
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 ^ 48);
}

template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
    write(x), putchar(c);
    write(c, y...);
}


template <typename T11, typename T22, typename T33>
struct T3
{
    T11 one;
    T22 tow;
    T33 three;

    bool operator<(const T3 other) const
    {
        if (one == other.one)
        {
            if (tow == other.tow) return three < other.three;
            return tow < other.tow;
        }
        return one < other.one;
    }

    T3()
    {
        one = tow = three = 0;
    }

    T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
    {
    }
};

template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
    if (x < y) x = y;
}

template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
    if (x > y) x = y;
}

typedef __int128 i128;
constexpr int N = 1e6 + 10;
constexpr i128 INF = 1e-20;
bool vis[N + 1];
vector<int> pri;
int eulr[N + 1];
ll pw[N + 1];

inline void init()
{
    pw[1] = eulr[1] = 1;
    forn(i, 2, N)
    {
        ++pw[i]; //注意每一个都有1这个因子
        for (int j = i; j <= N; j += i) pw[j] += i; //调和级数预处理
        if (!vis[i]) pri.push_back(i), eulr[i] = i - 1;
        for (const ll j : pri)
        {
            if (i * j > N) break;
            vis[i * j] = true;
            if (i % j == 0)
            {
                eulr[i * j] = eulr[i] * j;
                break;
            }
            eulr[i * j] = eulr[i] * eulr[j];
        }
    }
}

struct Point
{
    i128 k, b;
    int idR;

    i128 getVal(const i128 x) const
    {
        return k * x + b;
    }

    Point operator-(const Point& other) const
    {
        return Point(k - other.k, b - other.b);
    }
};

inline i128 operator*(const Point& x, const Point& y)
{
    return x.k * y.b - x.b * y.k;
}

deque<Point> hull;

//头部加入
inline void add(const Point& curr)
{
    while (hull.size() > 1 and (hull[1] - hull[0]) * (hull[0] - curr) <= 0) hull.pop_front();
    hull.push_front(curr);
}

//凸包上二分,越靠左的下标越小,所以我们注意严格小于才往右找
inline pair<i128, int> query(const ll x)
{
    int l = 0, r = hull.size() - 1;
    while (l < r)
    {
        const int mid = l + r + 1 >> 1;
        if (mid and hull[mid - 1].getVal(x) < hull[mid].getVal(x)) l = mid;
        else r = mid - 1;
    }
    return make_pair(hull[l].getVal(x), hull[l].idR);
}

int n, q;
ll a[N];
i128 k[N], b[N];
vector<tii> qu;
pair<i128, int> ans[N];

inline void solve()
{
    cin >> n >> q;
    forn(i, 1, n) cin >> a[i];
    init();
    //预处理(k,b)
    forn(i, 1, n) k[i] = k[i - 1] + eulr[a[i]], b[i] = b[i - 1] - a[i] * pw[a[i]];
    forn(i, 1, q)
    {
        int u, l;
        cin >> u >> l;
        qu.emplace_back(l, u, i);
    }
    //对l降序排列
    sort(all(qu), greater());
    int idx = n;
    for (const auto [l,u,id] : qu)
    {
        //没加入的(k,b)给加入
        while (idx >= l) add(Point(k[idx], b[idx], idx)), idx--;
        ans[id] = query(u);
        ans[id].first -= u * k[l - 1] + b[l - 1];
    }
    forn(i, 1, q) write(' ', ans[i].first, ans[i].second), putchar(endl);
}

signed int main()
{
    // MyFile
    Spider
    //------------------------------------------------------
    // clock_t start = clock();
    int test = 1;
    //    read(test);
    // cin >> test;
    forn(i, 1, test) solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
    // clock_t end = clock();
    // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

\[这样做的预处理是:\ O(V\ln{V}),V=a_{max} \]

\[修改和查询为:\ O(q\log{n}) \]

凸包+因数和线性筛预处理
#include <bits/stdc++.h>

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

// #define isPbdsFile

#ifdef isPbdsFile

#include <bits/extc++.h>

#else

#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>

#endif

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

template <typename T>
int disc(T* a, int n)
{
    return unique(a + 1, a + n + 1) - (a + 1);
}

template <typename T>
T lowBit(T x)
{
    return x & -x;
}

template <typename T>
T Rand(T l, T r)
{
    static mt19937 Rand(time(nullptr));
    uniform_int_distribution<T> dis(l, r);
    return dis(Rand);
}

template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
    return (a % b + b) % b;
}

template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
    a %= c;
    T1 ans = 1;
    for (; b; b >>= 1, (a *= a) %= c) if (b & 1) (ans *= a) %= c;
    return modt(ans, c);
}

template <typename T>
void read(T& x)
{
    x = 0;
    T sign = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-') sign = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    x *= sign;
}

template <typename T, typename... U>
void read(T& x, U&... y)
{
    read(x);
    read(y...);
}

template <typename T>
void write(T x)
{
    if (typeid(x) == typeid(char)) return;
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 ^ 48);
}

template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
    write(x), putchar(c);
    write(c, y...);
}


template <typename T11, typename T22, typename T33>
struct T3
{
    T11 one;
    T22 tow;
    T33 three;

    bool operator<(const T3 other) const
    {
        if (one == other.one)
        {
            if (tow == other.tow) return three < other.three;
            return tow < other.tow;
        }
        return one < other.one;
    }

    T3()
    {
        one = tow = three = 0;
    }

    T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
    {
    }
};

template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
    if (x < y) x = y;
}

template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
    if (x > y) x = y;
}

typedef __int128 i128;
constexpr int N = 1e6 + 10;
bool vis[N + 1];
vector<int> pri;
int eulr[N + 1];
ll pw[N + 1], num[N + 1];
//线性预处理欧拉函数和因数和
inline void init()
{
    pw[1] = eulr[1] = 1;
    forn(i, 2, N)
    {
        if (!vis[i]) pri.push_back(i), eulr[i] = i - 1, pw[i] = num[i] = i + 1;
        for (const ll j : pri)
        {
            if (i * j > N) break;
            vis[i * j] = true;
            if (i % j == 0)
            {
                eulr[i * j] = eulr[i] * j;
                num[i * j] = num[i] * j + 1;
                pw[i * j] = pw[i] / num[i] * (num[i] * j + 1);
                break;
            }
            num[i * j] = j + 1;
            pw[i * j] = pw[i] * pw[j];
            eulr[i * j] = eulr[i] * eulr[j];
        }
    }
}

struct Point
{
    i128 k, b;
    int idR;

    i128 getVal(const i128 x) const
    {
        return k * x + b;
    }

    Point operator-(const Point& other) const
    {
        return Point(k - other.k, b - other.b);
    }
};

inline i128 operator*(const Point& x, const Point& y)
{
    return x.k * y.b - x.b * y.k;
}

deque<Point> hull;
//头部加入(k,b)
inline void add(const Point& curr)
{
    while (hull.size() > 1 and (hull[1] - hull[0]) * (hull[0] - curr) <= 0) hull.pop_front();
    hull.push_front(curr);
}

//凸包上二分,注意一下头部的下标偏小,所以非严格大于就别往右找了,找最小的r
inline pair<i128, int> query(const ll x)
{
    int l = 0, r = hull.size() - 1;
    while (l < r)
    {
        const int mid = l + r + 1 >> 1;
        if (hull[mid - 1].getVal(x) < hull[mid].getVal(x)) l = mid;
        else r = mid - 1;
    }
    return make_pair(hull[l].getVal(x), hull[l].idR);
}

int n, q;
ll a[N];
i128 k[N], b[N];
vector<tii> qu;
pair<i128, int> ans[N];

inline void solve()
{
    cin >> n >> q;
    forn(i, 1, n) cin >> a[i];
    init();
    //预处理出(k,b)
    forn(i, 1, n) k[i] = k[i - 1] + eulr[a[i]], b[i] = b[i - 1] - a[i] * pw[a[i]];
    forn(i, 1, q)
    {
        int u, l;
        cin >> u >> l;
        qu.emplace_back(l, u, i);
    }
    //l降序排列
    sort(all(qu), greater());
    int idx = n;
    for (const auto [l,u,id] : qu)
    {
        //加入新的(k,b)
        while (idx >= l) add(Point(k[idx], b[idx], idx)), idx--;
        ans[id] = query(u);
        ans[id].first -= u * k[l - 1] + b[l - 1];
    }
    forn(i, 1, q) write(' ', ans[i].first, ans[i].second), putchar(endl);
}

signed int main()
{
    // MyFile
    Spider
    //------------------------------------------------------
    // clock_t start = clock();
    int test = 1;
    //    read(test);
    // cin >> test;
    forn(i, 1, test) solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
    // clock_t end = clock();
    // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

\[这样做的预处理是:\ O(V),V=a_{max} \]

\[修改和查询为:\ O(q\log{n}) \]

李超树做法

关于李超树求最值:

  1. 如果维护的非值域线段,那么就是单 \(\log{MX}\),否则为 \(\log^2{MX}\)

  2. 由于是标记永久化,所以并不支持删除,但可以用可撤销或者可持久化进行 变向删除,同时支持动态开点。

  3. 李超树越上层的直线越优,本题由于比较除了值以外,还要比较同值情况下标小的更优,所以需要重载下比较函数、比较符。

我们从左往右和从右往左都讲讲。

从左往右,我们发现是相当于李超树中逐渐删除部分直线 \((k,b)\),那么我们可以倒序加入直线 \((k,b)\) 以后,按照 \(l\) 升序从左往右不断撤销操作,从而实现删除操作。由于每个加入操作都是 \(\log{MX}\),所以撤销同理。值域挺大的 \(2e7\),可以考虑动态开点,但这题给的 \(1G\),我们又只保存线段的 \(id\),所以直接上静态李超树了。

升序 $l$ + 可撤销李超树
#include <bits/stdc++.h>

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

// #define isPbdsFile

#ifdef isPbdsFile

#include <bits/extc++.h>

#else

#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>

#endif

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

template <typename T>
int disc(T* a, int n)
{
    return unique(a + 1, a + n + 1) - (a + 1);
}

template <typename T>
T lowBit(T x)
{
    return x & -x;
}

template <typename T>
T Rand(T l, T r)
{
    static mt19937 Rand(time(nullptr));
    uniform_int_distribution<T> dis(l, r);
    return dis(Rand);
}

template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
    return (a % b + b) % b;
}

template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
    a %= c;
    T1 ans = 1;
    for (; b; b >>= 1, (a *= a) %= c) if (b & 1) (ans *= a) %= c;
    return modt(ans, c);
}

template <typename T>
void read(T& x)
{
    x = 0;
    T sign = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-') sign = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    x *= sign;
}

template <typename T, typename... U>
void read(T& x, U&... y)
{
    read(x);
    read(y...);
}

template <typename T>
void write(T x)
{
    if (typeid(x) == typeid(char)) return;
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 ^ 48);
}

template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
    write(x), putchar(c);
    write(c, y...);
}


template <typename T11, typename T22, typename T33>
struct T3
{
    T11 one;
    T22 tow;
    T33 three;

    bool operator<(const T3 other) const
    {
        if (one == other.one)
        {
            if (tow == other.tow) return three < other.three;
            return tow < other.tow;
        }
        return one < other.one;
    }

    T3()
    {
        one = tow = three = 0;
    }

    T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
    {
    }
};

template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
    if (x < y) x = y;
}

template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
    if (x > y) x = y;
}

typedef __int128 i128;
constexpr int N = 1e6 + 10;
constexpr int MX = 2e7;
constexpr i128 INF = -1e25;
bool vis[N + 1];
vector<int> pri;
int eulr[N + 1];
ll pw[N + 1], num[N + 1];
int n, q;

inline void init()
{
    pw[1] = eulr[1] = 1;
    forn(i, 2, N)
    {
        if (!vis[i]) pri.push_back(i), eulr[i] = i - 1, pw[i] = num[i] = i + 1;
        for (const ll j : pri)
        {
            if (i * j > N) break;
            vis[i * j] = true;
            if (i % j == 0)
            {
                eulr[i * j] = eulr[i] * j;
                num[i * j] = num[i] * j + 1;
                pw[i * j] = pw[i] / num[i] * (num[i] * j + 1);
                break;
            }
            num[i * j] = j + 1;
            pw[i * j] = pw[i] * pw[j];
            eulr[i * j] = eulr[i] * eulr[j];
        }
    }
}

stack<pii> back;
int cnt[N];

struct Seg
{
    i128 k, b;

    i128 getVal(const i128 x) const
    {
        return k * x + b;
    }
} seg[N];

int segId[MX << 2];

//重写比较,值相同比下标,线段ID即为下标
inline bool check(const int idx, const int idy, const int x)
{
    const i128 a = seg[idx].getVal(x);
    const i128 b = seg[idy].getVal(x);
    if (a == b) return idx > idy;
    return a < b;
}

//修改时将操作保留在撤销栈中
inline void add(const int curr, int val, const int l = 1, const int r = MX)
{
    if (!segId[curr])
    {
        back.emplace(curr, segId[curr]);
        segId[curr] = val;
        return;
    }
    const int mid = l + r >> 1;
    if (check(segId[curr], val, mid))
    {
        back.emplace(curr, segId[curr]);
        swap(segId[curr], val);
    }
    if (check(segId[curr], val, l)) add(ls(curr), val, l, mid);
    if (check(segId[curr], val, r)) add(rs(curr), val, mid + 1, r);
}

typedef pair<i128, int> pAns;

//重写比较,值相同比下标谁更小,线段ID即为下标
inline bool operator<(const pAns& x, const pAns& y)
{
    if (x.first != y.first) return x.first < y.first;
    return x.second > y.second;
}

inline void merge(pAns& curr, const pAns& other)
{
    if (curr < other) curr = other;
}

inline pAns query(const int curr, const int val, const int l = 1, const int r = MX)
{
    if (!segId[curr]) return pAns(INF, n + 1);
    auto ans = pair(seg[segId[curr]].getVal(val), segId[curr]);
    if (l == r) return ans;
    const int mid = l + r >> 1;
    if (val <= mid) merge(ans, query(ls(curr), val, l, mid));
    else merge(ans, query(rs(curr), val, mid + 1, r));
    return ans;
}

ll a[N];
i128 k[N], b[N];
vector<tii> qu;
pAns ans[N];

inline void solve()
{
    cin >> n >> q;
    forn(i, 1, n) cin >> a[i];
    init();
    forn(i, 1, n) k[i] = k[i - 1] + eulr[a[i]], b[i] = b[i - 1] - a[i] * pw[a[i]];
    //倒序加入,并记录每次栈中剩余操作数量
    forv(i, n, 1)
    {
        seg[i] = Seg{k[i], b[i]};
        add(1, i);
        cnt[i] = back.size();
    }
    forn(i, 1, q)
    {
        int u, l;
        cin >> u >> l;
        qu.emplace_back(l, u, i);
    }
    //升序排列
    sort(all(qu));
    for (const auto [l,u,id] : qu)
    {
        //可撤销李超树撤销操作即为删除操作
        while (back.size() > cnt[l])
        {
            const auto [curr,segVal] = back.top();
            segId[curr] = segVal;
            back.pop();
        }
        auto [val, idx] = query(1, u);
        val -= u * k[l - 1] + b[l - 1];
        ans[id] = pAns(val, idx);
    }
    forn(i, 1, q) write(' ', ans[i].first, ans[i].second), putchar(endl);
}

signed int main()
{
    // MyFile
    Spider
    //------------------------------------------------------
    // clock_t start = clock();
    int test = 1;
    //    read(test);
    // cin >> test;
    forn(i, 1, test) solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
    // clock_t end = clock();
    // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

\[这样做的预处理是:\ O(V),V=a_{max} \]

\[修改和查询为:\ O((n+q)\log{n}) \]

倒序,就是普通的李超树了,直接不断地加入线段和做值域查询。

降序 $l$ + 普通李超树查询
#include <bits/stdc++.h>

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

// #define isPbdsFile

#ifdef isPbdsFile

#include <bits/extc++.h>

#else

#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>

#endif

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

template <typename T>
int disc(T* a, int n)
{
    return unique(a + 1, a + n + 1) - (a + 1);
}

template <typename T>
T lowBit(T x)
{
    return x & -x;
}

template <typename T>
T Rand(T l, T r)
{
    static mt19937 Rand(time(nullptr));
    uniform_int_distribution<T> dis(l, r);
    return dis(Rand);
}

template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
    return (a % b + b) % b;
}

template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
    a %= c;
    T1 ans = 1;
    for (; b; b >>= 1, (a *= a) %= c) if (b & 1) (ans *= a) %= c;
    return modt(ans, c);
}

template <typename T>
void read(T& x)
{
    x = 0;
    T sign = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-') sign = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    x *= sign;
}

template <typename T, typename... U>
void read(T& x, U&... y)
{
    read(x);
    read(y...);
}

template <typename T>
void write(T x)
{
    if (typeid(x) == typeid(char)) return;
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 ^ 48);
}

template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
    write(x), putchar(c);
    write(c, y...);
}


template <typename T11, typename T22, typename T33>
struct T3
{
    T11 one;
    T22 tow;
    T33 three;

    bool operator<(const T3 other) const
    {
        if (one == other.one)
        {
            if (tow == other.tow) return three < other.three;
            return tow < other.tow;
        }
        return one < other.one;
    }

    T3()
    {
        one = tow = three = 0;
    }

    T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
    {
    }
};

template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
    if (x < y) x = y;
}

template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
    if (x > y) x = y;
}

typedef __int128 i128;
constexpr int N = 1e6 + 10;
constexpr int MX = 2e7;
constexpr i128 INF = -1e25;
bool vis[N + 1];
vector<int> pri;
int eulr[N + 1];
ll pw[N + 1], num[N + 1];

inline void init()
{
    pw[1] = eulr[1] = 1;
    forn(i, 2, N)
    {
        if (!vis[i]) pri.push_back(i), eulr[i] = i - 1, pw[i] = num[i] = i + 1;
        for (const ll j : pri)
        {
            if (i * j > N) break;
            vis[i * j] = true;
            if (i % j == 0)
            {
                eulr[i * j] = eulr[i] * j;
                num[i * j] = num[i] * j + 1;
                pw[i * j] = pw[i] / num[i] * (num[i] * j + 1);
                break;
            }
            num[i * j] = j + 1;
            pw[i * j] = pw[i] * pw[j];
            eulr[i * j] = eulr[i] * eulr[j];
        }
    }
}

struct Seg
{
    i128 k, b;

    i128 getVal(const i128 x) const
    {
        return k * x + b;
    }
} seg[N];

int segId[MX << 2];

inline bool check(const int idx, const int idy, const int x)
{
    const i128 a = seg[idx].getVal(x);
    const i128 b = seg[idy].getVal(x);
    if (a == b) return idx > idy;
    return a < b;
}

inline void add(const int curr, int val, const int l = 1, const int r = MX)
{
    if (!segId[curr])
    {
        segId[curr] = val;
        return;
    }
    const int mid = l + r >> 1;
    if (check(segId[curr], val, mid)) swap(segId[curr], val);
    if (check(segId[curr], val, l)) add(ls(curr), val, l, mid);
    if (check(segId[curr], val, r)) add(rs(curr), val, mid + 1, r);
}

typedef pair<i128, int> pAns;

inline bool operator<(const pAns& x, const pAns& y)
{
    if (x.first != y.first) return x.first < y.first;
    return x.second > y.second;
}

inline void merge(pAns& curr, const pAns& other)
{
    if (curr < other) curr = other;
}

inline pAns query(const int curr, const int val, const int l = 1, const int r = MX)
{
    if (!segId[curr]) return pAns(INF, INF);
    auto ans = pair(seg[segId[curr]].getVal(val), segId[curr]);
    if (l == r) return ans;
    const int mid = l + r >> 1;
    if (val <= mid) merge(ans, query(ls(curr), val, l, mid));
    else merge(ans, query(rs(curr), val, mid + 1, r));
    return ans;
}

int n, q;
ll a[N];
i128 k[N], b[N];
vector<tii> qu;
pair<i128, int> ans[N];

inline void solve()
{
    cin >> n >> q;
    forn(i, 1, n) cin >> a[i];
    init();
    forn(i, 1, n) k[i] = k[i - 1] + eulr[a[i]], b[i] = b[i - 1] - a[i] * pw[a[i]];
    forn(i, 1, n) seg[i] = Seg{k[i], b[i]};
    forn(i, 1, q)
    {
        int u, l;
        cin >> u >> l;
        qu.emplace_back(l, u, i);
    }
    sort(all(qu), greater());
    //倒序遍历l
    int idx = n + 1;
    for (const auto [l,u,id] : qu)
    {
        while (idx > l) add(1, --idx);
        ans[id] = query(1, u);
        ans[id].first -= u * k[l - 1] + b[l - 1];
    }
    forn(i, 1, q) write(' ', ans[i].first, ans[i].second), putchar(endl);
}

signed int main()
{
    // MyFile
    Spider
    //------------------------------------------------------
    // clock_t start = clock();
    int test = 1;
    //    read(test);
    // cin >> test;
    forn(i, 1, test) solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
    // clock_t end = clock();
    // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

\[这样做的预处理是:\ O(V),V=a_{max} \]

\[修改和查询为:\ O((n+q)\log{n}) \]

posted @ 2024-07-06 23:52  Athanasy  阅读(131)  评论(0编辑  收藏  举报