你有 n n n个盒子,每个盒子内存在可能有黑球和白球中的一种,打开每个盒子都有一个代价 w i w_i wi,你还有一次询问裁判的机会,当然询问裁判代价为 C C C,你需要告诉裁判这 n n n个盒子每个盒子里面的球颜色,你需要花费的最小代价是多少?
考点:思维
首先我们不询问裁判的话,我们就要把 n n n个盒子全部打开,代价为 ∑ i = 1 n w i \sum\limits_{i=1}^nw_i i=1∑nwi
如果我们询问裁判,那么想想我们是不是最多只需要打开 n − 1 n-1 n−1个盒子,并且如果用 0 0 0代表白球 1 1 1代表黑球如下分析。
考虑开 0 0 0次盒子结束,如果裁判告诉我们有 n n n个白球或者 n n n个黑球用二进制表示那就是 0000 0000 0000或 1111 1111 1111这样的时候结束,那么我们这次代价就只有 C C C。
考虑开 1 1 1次盒子结束,那么我们要先开最小花费的那个,并且结束的要求是 1000 1000 1000或者 0111 0111 0111,并且还需要对上裁判给你的颜色,所以这里应该概率多乘一个 1 2 \frac{1}{2} 21,那么这种概率计算为 1 2 n − 1 \frac{1}{2}^{n-1} 21n−1
考虑开 2 2 2次盒子结束,那么我们结束的要求就是 . 100 .100 .100或者 . 011 .011 .011,那么这种概率计算为 1 2 n − 2 \frac{1}{2}^{n-2} 21n−2
以此类推,把开盒子代价排序然后求前缀和处理就行了。
const int N = 1e6 + 7;
int n;
double C;
double p[N];
int solve() {
cin >> n >> C;
for (int i = 1; i <= n; ++i) cin >> p[i];
sort(p + 1, p + 1 + n);
for (int i = 1; i <= n; ++i) p[i] += p[i - 1];
double res = C;
for (int i = 1; i < n; ++i) res += p[i] * pow(0.5, n - i);
res = min(res, p[n]);
printf("%.12f\n", res);
return 1;
}
你和对手进行乒乓球比赛,你们原本进行了 n ( 1 ≤ n ≤ 1 0 6 ) n(1\le n\le 10^6) n(1≤n≤106)局比赛, W W W代表你赢下了第 i i i局, L L L代表你输掉了第 i i i局。
现在我可以操控这个比赛规则,让游戏赛制变成在赢下 i i i球制,也就是我在赢下 i i i球后,如果和对手比分差值大于等于 2 2 2我就拿下一个小分,否则就是对手拿下一个小分,如果差值为 0 0 0或者 1 1 1那么接着进行下一局,那么我在 i i i球赛制下,进行完 n n n局比赛我能赢得的小分用 f ( i ) f(i) f(i)表示。
题目求 ∑ i = 1 n f ( i ) ∗ ( n + 1 ) i − 1 \displaystyle \sum\limits_{i=1}^nf(i)*(n+1)^{i-1} i=1∑nf(i)∗(n+1)i−1
考点:预处理模拟题
首先我们可以预处理出从 [ 1 , i ] [1,i] [1,i]这些位置里面有多少个 W W W和多少个 L L L,这样用前缀和的形式就可以找到任意区间内 W W W和 L L L的数量。
并且我们可以知道第 i i i个 W W W出现的下标在哪里,以及第 i i i个 L L L出现的下标在哪里。
那么当我们枚举这个 i i i球赛制的时候,我们就可以知道上一次加小分的位置在哪里,从这个位置往后 i i i个 W W W就是我下一次赢球的最小下标,同理可以找到对手赢球的最小下标。
将这两个下标取 min \min min之后局面就会出现两种;
这样我们发现每次枚举 i i i球赛制的话 p o s pos pos最小往后移动 i i i步,那么总的移动次数大概就是 n 1 + n 2 + n 3 . . . n n = n log n \displaystyle \frac{n}{1}+\frac{n}{2}+\frac{n}{3}...\frac{n}{n}=n\log n 1n+2n+3n...nn=nlogn
时间复杂度就是 O ( n log n ) O(n\log n) O(nlogn)
#define ms(__x__,__val__) memset(__x__, __val__, sizeof(__x__))
const int N = 1e6 + 7;
ll n, m;
char s[N];
int cntw[N], cntl[N];
int posw[N << 1], posl[N << 1];
int tiebreak[N];
int solve() {
n = read();
scanf("%s", s + 1);
ms(posw,0x3f);ms(posl,0x3f);
for (int i = 1; i <= n; ++i) {
cntw[i] = cntw[i - 1];
cntl[i] = cntl[i - 1];
if (s[i] == 'W') {
++cntw[i];
posw[cntw[i]] = i;
}
else {
++cntl[i];
posl[cntl[i]] = i;
}
}
tiebreak[n - 1] = tiebreak[n] = n + 1; // 平局数组
for (int i = n - 2; i >= 1; --i) {
tiebreak[i] = s[i + 1] == s[i + 2] ? i + 2 : tiebreak[i + 2];
}
int res = 0, xs = 1;
for (int i = 1; i <= n; ++i) {
int l = 0, r = 0;
int cnt = 0;
while (l < n) {
int W = posw[cntw[r] + i];
int L = posl[cntl[r] + i];
r = min(L, W);
if (r > n) break;
if (abs((cntw[r] - cntw[l]) - (cntl[r] - cntl[l])) >= 2) {
if (cntw[r] - cntw[l] > cntl[r] - cntl[l])
++cnt;
l = r;
continue;
}
// 之前的r一定有人领先1分
++r; // 要么平局 要么赢下
if (r > n) break;
if (abs((cntw[r] - cntw[l]) - (cntl[r] - cntl[l])) >= 2) {
if (cntw[r] - cntw[l] > cntl[r] - cntl[l])
++cnt;
l = r;
continue;
}
r = tiebreak[r];
if (r > n) break;
if (cntw[r] - cntw[l] > cntl[r] - cntl[l])
++cnt;
l = r;
}
res = (res + cnt * xs) % MOD;
xs = xs * (n + 1) % MOD;
}
print(res);
return 1;
}
给你两个字符串 A , B A,B A,B,长度在 5 ⋅ 1 0 3 5·10^3 5⋅103级别,询问在两者的全部子序列中,长度相等,并且 a < b aa<b的数量有多少个?
考点:相同子序列数量
如果直接考虑 a < b aa<b,因为子序列并不知道第几个位置不同,所以这个并不好处理,但是我们一定可以把这个的子序列分成 3 3 3段。
第一段:子序列前 i − 1 i-1 i−1个字符都相同。
第二段: a i < b i a_i
第三段:剩余长度任意挑选子序列。
先考虑如何求解任意两个位置 A [ 1 : i ] , B [ 1 : j ] A[1:i],B[1:j] A[1:i],B[1:j]相同子序列数量,这个用动态规划处理。
我们用 f i , j f_{i,j} fi,j代表以 A A A前 i i i个字符, B B B前 j j j个字符的相同子序列数量,那么我们可以写出下面的转移方程。
f i , j = f i − 1 , j + f i , j − 1 − f i − 1 , j − 1 A i ≠ B j f i , j = f i − 1 , j + f i , j − 1 − f i − 1 , j − 1 + ( f i − 1 , j − 1 + 1 ) A i = B j f_{i,j} = f_{i-1,j}+f_{i,j-1}-f_{i-1,j-1} \ \ \ \ \ \ \ A_i\neq B_j\\f_{i,j}=f_{i-1,j}+f_{i,j-1}-f_{i-1,j-1}+(f_{i-1,j-1}+1)\ \ \ \ \ \ \ \ A_i=B_j fi,j=fi−1,j+fi,j−1−fi−1,j−1 Ai=Bjfi,j=fi−1,j+fi,j−1−fi−1,j−1+(fi−1,j−1+1) Ai=Bj
当 A i ≠ B j A_i\neq B_j Ai=Bj的时候减掉的是重复计算的,当 A i = B j A_i=B_j Ai=Bj的时候加上的是一定选他们结尾的时候前面相同的,加上前面一个不选。
好了我们已经 O ( ∣ A ∣ ⋅ ∣ B ∣ ) O(|A|·|B|) O(∣A∣⋅∣B∣)预处理出了 f i , j f_{i,j} fi,j数组了,下面就要考虑后面长度任选怎么处理。
如果我们枚举到 A A A串后面还剩长度为 x x x, B B B串后面长度为 y y y,那么等价与求 ∑ i = 0 min ( x , y ) C x i ∗ C y i \displaystyle \sum\limits_{i=0}^{\min(x,y)}C_x^i*C_y^i i=0∑min(x,y)Cxi∗Cyi
上面这个式子可以做个等价变换,首先我们知道 C x i = C x x − i C_x^i=C_x^{x-i} Cxi=Cxx−i,那么就变成了我有两堆球,我要在左边拿出 x − i x-i x−i个球,右边拿出 i i i个球的方案数总和,并且提供这个乘法之后求合我们可以判断这些球都是完全不一样的,那不就等价于我一共有 x + y x+y x+y个球,要拿出 x x x个球的方案数吗,所以我们得到 ∑ i = 0 min ( x , y ) C x i ∗ C y i = C x + y x \displaystyle\sum_{i=0}^{\min(x,y)}C_x^i*C_y^i=C_{x+y}^x i=0∑min(x,y)Cxi∗Cyi=Cx+yx
接下来就只要计算答案就行了,总的时间复杂度是 O ( ∣ A ∣ ⋅ ∣ B ∣ ) O(|A|·|B|) O(∣A∣⋅∣B∣)。
const int N = 5e3 + 7;
ll n, m;
int f[N][N];
ll jc[N << 1], inv[N << 1];
void init() {
jc[0] = 1;
for (int i = 1; i < N * 2; ++i) jc[i] = jc[i - 1] * i % MOD;
inv[N * 2 - 1] = qpow(jc[N * 2 - 1], MOD - 2, MOD);
for (int i = N * 2 - 2; ~i; --i) inv[i] = inv[i + 1] * (i + 1) % MOD;
}
ll C(int n, int m) {
return jc[n] * inv[m] % MOD * inv[n - m] % MOD;
}
char s[N], t[N];
int solve() {
scanf("%s", s + 1);
scanf("%s", t + 1);
n = strlen(s + 1), m = strlen(t + 1);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (s[i] == t[j]) {
f[i][j] = (f[i - 1][j] + f[i][j - 1] + 1) % MOD;
}
else {
f[i][j] = ((f[i - 1][j] + f[i][j - 1]) % MOD - f[i - 1][j - 1] + MOD) % MOD;
}
}
}
int res = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (s[i] < t[j]) {
res = (res + (f[i - 1][j - 1] + 1) * C(n - i + m - j, n - i) % MOD) % MOD;
}
}
}
print(res);
return 1;
}
按逆时针给出凸包上的 n n n个点,你要在凸包里面找到一个点 P P P,让 ∠ A i P A i + 1 \angle A_iPA_{i+1} ∠AiPAi+1的最小值最大,然后输出这个最大的最小值。
考点:三分
比较明显的容易发现如果我们固定 x x x,那么 y y y的相关函数一定是一个单峰函数。
同理固定 y y y,那么 x x x的相关函数也一定是单峰函数。
那么就直接用三分套三分就可以求解了,复杂度 O ( n log 3 2 W ) O(n\log_3^2 W) O(nlog32W)。
const int INF = 0x3f3f3f3f;
const int N = 100 + 7;
const double PI = acos(-1);
ll n, m;
pai p[N];
double xmax = -INF, xmin = INF, ymax = -INF, ymin = INF;
pai operator - (const pai& a, const pai& b) {
return { a.first - b.first,a.second - b.second };
}
double operator * (const pai& a, const pai& b) {
return a.first * b.first + a.second * b.second;
}
double getLen(pai a) {
return sqrt(a * a);
}
double getAngle(pai a, pai b) {
return acos(a * b / getLen(a) / getLen(b));
}
double check(pai x) {
double res = INF;
for (int i = 1; i <= n; ++i) {
res = min(res, getAngle(p[i] - x, p[i + 1] - x));
}
return res;
}
double calc(double x) {
double l = ymin, r = ymax;
for (int i = 1; i <= 100; ++i) {
double lmid = l + (r - l) / 3.0;
double rmid = r - (r - l) / 3.0;
if (check({ x,lmid }) > check({ x,rmid })) {
r = rmid;
}
else {
l = lmid;
}
}
return check({ x,l });
}
int solve() {
n = read();
for (int i = 1; i <= n; ++i) {
auto& [x, y] = p[i];
x = read(), y = read();
xmax = max(xmax, x);
xmin = min(xmin, x);
ymax = max(ymax, y);
ymin = min(ymin, y);
}
p[n + 1] = p[1];
double l = xmin, r = xmax;
for (int i = 1; i <= 100; ++i) {
double lmid = l + (r - l) / 3.0;
double rmid = r - (r - l) / 3.0;
if (calc(lmid) > calc(rmid)) {
r = rmid;
}
else {
l = lmid;
}
}
printf("%.12f\n", calc(l) * 180 / PI);
return 1;
}
给你 a , b , c a,b,c a,b,c,你要让 l c m ( a + x , b + y ) = c lcm(a+x,b+y)=c lcm(a+x,b+y)=c的前提下,最小的 x + y x+y x+y是什么,并且给出的 c c c是分解质因数的形式, c = ∏ p i q i c=\prod\limits p_i^{q_i} c=∏piqi。
1 ≤ a , b , c ≤ 1 0 32 , ∑ i = 1 n q i ≤ 18 1\le a,b,c\le 10^{32},\sum\limits_{i=1}^nq_i\le18 1≤a,b,c≤1032,i=1∑nqi≤18
考点:二进制枚举
看到这个 c c c给的这么形式化,就要围绕这个去弄,而且幂次之和不超过 18 18 18,很明显的让我们去二进制枚举。
那么我们就可以暴力的 d f s dfs dfs预处理出当前状态 s s s,可以的全部花费,那么要更新还有一个前提,就是之前的 p r o d prod prod最起码应该大于 a a a,才可以更新前半部分,大于 b b b才可以更新后半部分,我们这样暴力乱搞之后对于花费大于 a , b a,b a,b的全部状态的最小花费都可以找出来。
接下来我们只需要再把这些东西的子集更新一下,注意因为我们 d f s dfs dfs乱搞的时候有一个 ≥ a \ge a ≥a的条件,可能 001 001 001这个状态就无法大于等于 a a a,这时候保留的仍然是一个无穷大的值,但是我们用 001 001 001这个状态更新应该可以由 111 111 111转移来的,所以我们要倒序的枚举一下子集,保证每个状态都找到最小值。
接下来去枚举这 n n n个幂次,左右怎么分保证 n n n个数都是 1 1 1的最小值了。
找出这个最小值,再减掉 a + b a+b a+b就是最终答案了。
#define ms(__x__,__val__) memset(__x__, __val__, sizeof(__x__))
inline __int128 read() { __int128 s = 0, w = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') w = -1; for (; isdigit(ch); ch = getchar()) s = (s << 1) + (s << 3) + (ch ^ 48); return s * w; }
inline void print(__int128 x, int op = 10) { if (!x) { putchar('0'); if (op) putchar(op); return; } char F[40]; __int128 tmp = x > 0 ? x : -x; if (x < 0)putchar('-'); int cnt = 0; while (tmp > 0) { F[cnt++] = tmp % 10 + '0'; tmp /= 10; } while (cnt > 0)putchar(F[--cnt]); if (op) putchar(op); }
const int N = (1<<18) + 7;
int n, m;
int p[20], q[20];
__int128 fa[N], fb[N], a, b, c;
void dfs(int dep, __int128 prod, int s) {
if (dep == n) {
if (prod >= a) fa[s] = min(fa[s], prod);
if (prod >= b) fb[s] = min(fb[s], prod);
return;
}
for (int i = 0; i <= q[dep]; ++i) {
if (i == q[dep]) s |= (1 << dep);
dfs(dep + 1, prod, s);
prod *= p[dep];
}
}
int solve() {
n = read();
m = (1 << n) - 1;
for (int i = 0; i < n; ++i) {
p[i] = read(), q[i] = read();
}
a = read(), b = read(), c = 1;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < q[i]; ++j) c *= p[i];
}
__int128 inf = c * 2;
ms(fa, 0x3f), ms(fb, 0x3f);
dfs(0, 1, 0);
for (int i = m; i >= 1; --i) {
for (int j = 0; j < n; ++j) {
if (i & (1 << j)) {
fa[i ^ (1 << j)] = min(fa[i ^ (1 << j)], fa[i]);
fb[i ^ (1 << j)] = min(fb[i ^ (1 << j)], fb[i]);
}
}
}
__int128 res = inf;
for (int i = 0; i <= m; ++i) {
res = min(res, fa[i] + fb[i ^ m]);
}
print(res - a - b);
return 1;
}
你要构造一个 n ⋅ m n·m n⋅m的 01 01 01矩阵,保证同行同列同对角的 3 3 3个位置不能是同一个字符。
考虑下面这样的构造
001100110011
110011001100
001100110011
110011001100
int solve() {
int n = read(), m = read();
for (int i = 0; i < n; ++i) {
if (i & 1) {
for (int j = 0; j < m; ++j) {
char ch = j % 4 <= 1 ? '0' : '1';
putchar(ch);
}
}
else {
for (int j = 0; j < m; ++j) {
char ch = j % 4 <= 1 ? '1' : '0';
putchar(ch);
}
}
puts("");
}
return 1;
}
你有 n ( 1 ≤ n ≤ 300 ) n(1\le n\le 300) n(1≤n≤300)个宝藏,他们分布在一些三维点 ( x i , y i , z i ) (x_i,y_i,z_i) (xi,yi,zi),他们还有一个各自的上升速度 t i t_i ti,他们第 k k k秒所在的位置为 ( x i , y i , z i + k ⋅ t i ) (x_i,y_i,z_i+k·t_i) (xi,yi,zi+k⋅ti)。你拿掉宝藏的代价为 x i + y i + z i + k ⋅ t i x_i+y_i+z_i+k·t_i xi+yi+zi+k⋅ti,你每秒只可以拿掉一个宝藏,问拿完 n n n个宝藏的最小代价是多少?
考点:最小权值匹配
很明显每秒只可以拿掉一个宝藏,拿完 n n n个宝藏一定要 n − 1 n-1 n−1秒,那么我们把 0 → n − 1 0\to n-1 0→n−1秒看成 n n n个点,这 n n n个物品也看成 n n n个点,那么就变成了一张有完全图的二分图。跑最小权值匹配问题,可以用 K M KM KM算法,我先用了下我自己的 m c m f mcmf mcmf算法跑最小费用最大流给 T T T飞了 … … …枯了。然后就去听群友的建议抄了一个常数比较优秀的mcmf算法。
封装的还是很好的,常数非常优秀只跑到 400 m s 400ms 400ms左右。
const int N = 600 + 7;
namespace atcoder {
template <class Cap, class Cost> struct mcf_graph {
public:
mcf_graph() {}
mcf_graph(int n) : _n(n), g(n) {}
int add_edge(int from, int to, Cap cap, Cost cost) {
assert(0 <= from && from < _n);
assert(0 <= to && to < _n);
int m = pos.size();
pos.push_back({ from, g[from].size() });
int from_id = g[from].size();
int to_id = g[to].size();
if (from == to) to_id++;
g[from].push_back(_edge{ to, to_id, cap, cost });
g[to].push_back(_edge{ from, from_id, 0, -cost });
return m;
}
struct edge {
int from, to;
Cap cap, flow;
Cost cost;
};
edge get_edge(int i) {
int m = pos.size();
assert(0 <= i && i < m);
auto _e = g[pos[i].first][pos[i].second];
auto _re = g[_e.to][_e.rev];
return edge{
pos[i].first, _e.to, _e.cap + _re.cap, _re.cap, _e.cost,
};
}
std::vector<edge> edges() {
int m = pos.size();
std::vector<edge> result(m);
for (int i = 0; i < m; i++) {
result[i] = get_edge(i);
}
return result;
}
std::pair<Cap, Cost> flow(int s, int t) {
return flow(s, t, std::numeric_limits<Cap>::max());
}
std::pair<Cap, Cost> flow(int s, int t, Cap flow_limit) {
return slope(s, t, flow_limit).back();
}
std::vector<std::pair<Cap, Cost>> slope(int s, int t) {
return slope(s, t, std::numeric_limits<Cap>::max());
}
std::vector<std::pair<Cap, Cost>> slope(int s, int t, Cap flow_limit) {
assert(0 <= s && s < _n);
assert(0 <= t && t < _n);
assert(s != t);
std::vector<Cost> dual(_n, 0), dist(_n);
std::vector<int> pv(_n), pe(_n);
std::vector<bool> vis(_n);
auto dual_ref = [&]() {
std::fill(dist.begin(), dist.end(),
std::numeric_limits<Cost>::max());
std::fill(pv.begin(), pv.end(), -1);
std::fill(pe.begin(), pe.end(), -1);
std::fill(vis.begin(), vis.end(), false);
struct Q {
Cost key;
int to;
bool operator<(Q r) const { return key > r.key; }
};
std::priority_queue<Q> que;
dist[s] = 0;
que.push(Q{ 0, s });
while (!que.empty()) {
int v = que.top().to;
que.pop();
if (vis[v]) continue;
vis[v] = true;
if (v == t) break;
for (int i = 0; i < g[v].size(); i++) {
auto e = g[v][i];
if (vis[e.to] || !e.cap) continue;
Cost cost = e.cost - dual[e.to] + dual[v];
if (dist[e.to] - dist[v] > cost) {
dist[e.to] = dist[v] + cost;
pv[e.to] = v;
pe[e.to] = i;
que.push(Q{ dist[e.to], e.to });
}
}
}
if (!vis[t]) {
return false;
}
for (int v = 0; v < _n; v++) {
if (!vis[v]) continue;
dual[v] -= dist[t] - dist[v];
}
return true;
};
Cap flow = 0;
Cost cost = 0, prev_cost_per_flow = -1;
std::vector<std::pair<Cap, Cost>> result;
result.push_back({ flow, cost });
while (flow < flow_limit) {
if (!dual_ref()) break;
Cap c = flow_limit - flow;
for (int v = t; v != s; v = pv[v]) {
c = std::min(c, g[pv[v]][pe[v]].cap);
}
for (int v = t; v != s; v = pv[v]) {
auto& e = g[pv[v]][pe[v]];
e.cap -= c;
g[v][e.rev].cap += c;
}
Cost d = -dual[s];
flow += c;
cost += c * d;
if (prev_cost_per_flow == d) {
result.pop_back();
}
result.push_back({ flow, cost });
prev_cost_per_flow = d;
}
return result;
}
private:
int _n;
struct _edge {
int to, rev;
Cap cap;
Cost cost;
};
std::vector<std::pair<int, int>> pos;
std::vector<std::vector<_edge>> g;
};
}
using namespace atcoder;
mcf_graph<int64_t, int64_t> mc(N);
int solve() {
int n = read();
int be = 0, ed = 2 * n + 2;
long long res = 0;
for (int i = 1; i <= n; ++i) {
int x = read(), y = read(), z = read(), v = read();
mc.add_edge(0, i, 1, 0);
mc.add_edge(n + i, ed, 1, 0);
res += x * x + y * y;
long long dep = z;
for (int j = 1; j <= n; ++j) {
mc.add_edge(i, n + j, 1, dep * dep);
dep += v;
}
}
print(res + mc.flow(be, ed).second);
return 1;
}
给你长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1\le n\le 10^5) n(1≤n≤105)的序列 a i a_i ai,并且有 Q ( 1 ≤ Q ≤ 200 ) Q(1\le Q\le 200) Q(1≤Q≤200)次查询,每次查询给出一个 k ( 1 ≤ k ≤ 1 0 9 ) k(1\le k\le 10^9) k(1≤k≤109),询问在 a a a中有多少个区间的最大值减掉最小值严格大于 k k k。
考点:双指针+ S T ST ST表
考虑到区间不会修改,那么查找区间最值这个问题就用 S T ST ST表维护就可以。
然后再看最大值减掉最小值严格大于 k k k,如果在 [ i , r ] [i,r] [i,r]这个区间符合要求,那么是不是在 [ r , n ] [r,n] [r,n]这些右区间都是合法的,因为随着数的加入,区间最大值一定不会变小,区间最小值一定不会变大,所以并不会变得不符合要求。
那么我们从左到右枚举 i i i,可以看出这个 r r r也是单调的,我们用 S T ST ST表维护这个 r r r就可以了。
时间复杂度 O ( n ⋅ m + n log n ) O(n·m+n\log n) O(n⋅m+nlogn)。
const int N = 1e5 + 7;
int st1[N][21], st2[N][21], Log2[N], a[N];
void pre() {
Log2[1] = 0;
Log2[2] = 1;
for (int i = 3; i < N; ++i) Log2[i] = Log2[i >> 1] + 1;
}
int queryMax(int l, int r) {
int s = Log2[r - l + 1];
return max(st1[l][s], st1[r - (1 << s) + 1][s]);
}
int queryMin(int l, int r) {
int s = Log2[r - l + 1];
return min(st2[l][s], st2[r - (1 << s) + 1][s]);
}
void sol() {
pre();
int n = read(), m = read();
for (int i = 1; i <= n; ++i) a[i] = st1[i][0] = st2[i][0] = read();
int t = Log2[n]; //区间最长的2次方
for (int j = 1; j <= t; ++j)
for (int i = 1; i + (1 << j) - 1 <= n; ++i)
st1[i][j] = max(st1[i][j - 1], st1[i + (1 << (j - 1))][j - 1]),
st2[i][j] = min(st2[i][j - 1], st2[i + (1 << (j - 1))][j - 1]);
while (m--) {
int k = read();
ll ans = 0;
int r = 1;
for (int i = 1; i <= n; ++i) {
while (r <= n && queryMax(i, r) - queryMin(i, r) <= k)
++r;
if (r == n + 1) break;
//cout << r << endl;
ans += n - r + 1;
}
print(ans);
}
}