[SNCPC2024] 2024 年陝西省大學生程式設計 J題猜質數II 題解

Athanasy發表於2024-07-06

題目連結:CF 或者 洛谷

PS: CF的得等上gym。

前提說明

其實在上個月就見到這題了,當時很想做這題,結果找不到做題連結,也不知道出處,原來是陝西省賽的捧杯題

個人評價覺得是一道很不錯的題,難度適中。

講解

其實題解寫的挺不錯的,比很多比賽的題解寫的詳細許多了。這裡站在我的角度分析這題吧:

先觀察要求的式子:

\[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}) \]

相關文章