[SNCPC2024] 2024 年陕西省大学生程序设计 J题猜质数II 题解
题目链接:CF 或者 洛谷
前提说明
其实在上个月就见到这题了,当时很想做这题,结果找不到做题链接,也不知道出处,原来是陕西省赛的捧杯题。
个人评价觉得是一道很不错的题,难度适中。
讲解
其实题解写的挺不错的,比很多比赛的题解写的详细许多了。这里站在我的角度分析这题吧:
先观察要求的式子:
其中
\(u\) 是题目给定查询的。看到这一坨就很麻烦,我们看看询问啥东西:
那看来肯定是需要变形了,我们重点分析一下:
那么我们得重点分析一下这里面的每种情况的贡献:
-
首先观察到 \(0\) 的情况,当 \(x>y\) 时,上述三点没有任何一点满足,所以我们只需要考虑 \(1\sim y\) 的数的贡献,直接分别考虑三种情况的贡献加起来就行了。
-
先考虑第二种情况,即 \(1< x\le y\) 中与 \(y\) 互质的数的个数,这个为欧拉函数的定义,即为 \(cnt=eulr[y]-1\),因为这里不包含 \(1\)。所以贡献为:\(cnt \times u\)。主要这里面 \(x>1\),所以少了一个 \(u\),为 \(x=1\) 的贡献。
-
考虑第三种情况,\(\gcd(x,y)=x\),说明 \(x\) 是 \(y\) 的因子,总贡献为:\(-X \times y\),其中 \(X\) 为所有符合题意的 \(x\) 的和,即为 \(y\) 的因子和。但要注意一下,这里 \(x \neq 1\),所以因子和里面要少个 \(-1 \times y\)。
-
考虑情况 \(1\),我们发现二三情况总共少了个 \(u-y\) 关于 \(x=1\) 的贡献,而 \(1\) 情况恰好补上了,那么我们可以这样处理 \(2\) 和 \(3\) 情况全部考虑 \(x=1\) 的贡献,然后不考虑 情况 \(1\),这样贡献也正确了。
这样一来由上述分析可知道:
我们记:\(eulr[x]\) 表示 \(x\) 的欧拉函数值,\(pw[x]\) 为关于 \(x\) 的所有因数和,包括自身。
那么:
而这里面的 \(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\):
每一项 \(val[i]\) 都是独立的,显然可以有:
我们容易知道 \(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\) 之类的查询方式。
我们观察
那么显然每个 \(r\) 都是一个 \(ans(r)=k_r \times u +b_r\),一个一次函数。
那么其实思路就很简单了,一堆 带限制的 一次函数最大值查询。
关于斜率最值查询有很多工具:半平面交、凸壳二分/三分、李超线段树...
本题就讲讲个人比较喜欢用的两种,凸壳和李超树怎么分析和书写吧,半平面交只会一些板子题,等后续更深了再补。
凸包做法
关于凸壳求最大值:
-
构建下凸壳。
-
凸壳上二分。
先考虑第一个问题,第一个问题我们需要将 \((k,b)\) 进行排序,然后再做单调队列。我们观察询问,\(r_i \ge l_i\),对于每个询问来说,它们的 \((k,b)\) 集合是不相同的,不可能每次都重建凸壳,那么这时候我们需要考虑一下如何让凸壳不重建:
观察:
这两个东西,当 \(r\) 变大,\(eulr[a_i]>0\),显然 \(k\) 单调递增。同理 \(a_i \times pw[a_i]>0\),所以 \(b\) 是单调递减的,当然我们的凸壳排序主要是跟 \(k\) 有关,那么我们观察到有:
这意味着我们如果从左往右遍历,只需要在凸壳最后加入新的 \((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;
}
凸包+因数和线性筛预处理
#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;
}
李超树做法
关于李超树求最值:
-
如果维护的非值域线段,那么就是单 \(\log{MX}\),否则为 \(\log^2{MX}\)。
-
由于是标记永久化,所以并不支持删除,但可以用可撤销或者可持久化进行 变向删除,同时支持动态开点。
-
李超树越上层的直线越优,本题由于比较除了值以外,还要比较同值情况下标小的更优,所以需要重载下比较函数、比较符。
我们从左往右和从右往左都讲讲。
从左往右,我们发现是相当于李超树中逐渐删除部分直线 \((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;
}
倒序,就是普通的李超树了,直接不断地加入线段和做值域查询。
降序 $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;
}