考虑线段树,显然操作 1,3,4 均能用线段树解决。 操作 2 并不好处理,如果直接硬除,操作复杂度为单次 $O(n)$。 考虑将其转化为便于线段树维护的操作。 对于区间 $[l,r]$,记 $p$ 为 $[l,r]$ 间的最大值,$q$ 为 $[l,r]$ 的最小值。 若 $p-\lfloor \frac{p}{d}\rfloor=q-\lfloor \frac{q}{d}\rfloor$,那么此时可以将这个操作转化为区间加法。 原因不难理解,显然 $a_i-\lfloor \frac{a_i}{d}\rfloor$ 单调不降,如果满足上述条件,显然 $a_i-\lfloor \frac{a_i}{d}\rfloor$ 都相等,那么直接作整体加(实际上是减)即可。 这种操作的复杂度如何? 根据直觉,其实会发现每次 2 操作 $a_i$ 减小得比 $\lfloor\frac{a_i}{d} \rfloor$ 快得多,主观上时间复杂度并不高。 严谨的时间复杂度证明需要势能分析。 (注:此处贺了 LOJ 讨论区的证明,加了点我自己的理解,大概率是错的,看个乐就行) 不妨设 $n,v$ 同阶。 定义势能为 $\sum \log_2 (|a_i-a_{i-1}|)$。 显然,开始时势能为 $O(n\log n)$。 一次区间加后,区间 $[l,r]$ 内部势能不变,两端各增加 $O(\log_2 n)$。总体来说变化量可以忽略(其实此时的势能为 $O((n+q)\log_2 n)$)。 每个势能的连续段在树上被分为 $\log$ 段,而每次区间除法操作珂以使两个相邻连续段之间的势能减 1。 相当于用 $\log$ 的代价减去了 1。 那么总的时间复杂度即为 $O((n+q)\log ^2 n)$。
#include <bits/stdc++.h> typedef long long ll; const ll INF = 1e18; int read() { int s = 0,f = 1; char c = getchar(); for(;c < '0'||c > '9';c = getchar()) if(c == '-')f = -1; for(;c >= '0'&&c <= '9';c = getchar()) s = (s << 1) + (s << 3) + (c ^ '0'); return s * f; } const int maxn = 1e5 + 5; int n,m; int ls[maxn << 2],rs[maxn << 2]; ll sum[maxn << 2],lz[maxn << 2],maxv[maxn << 2],minv[maxn << 2]; void pushup(int i) { sum[i] = sum[i << 1] + sum[i << 1 | 1]; maxv[i] = std::max(maxv[i << 1] , maxv[i << 1 | 1]); minv[i] = std::min(minv[i << 1] , minv[i << 1 | 1]); return ; } void pushdown(int i) { if(!lz[i])return ; lz[i << 1] += lz[i]; lz[i << 1 | 1] += lz[i]; minv[i << 1] += lz[i]; minv[i << 1 | 1] += lz[i]; maxv[i << 1] += lz[i]; maxv[i << 1 | 1] += lz[i]; sum[i << 1] += 1ll * (rs[i << 1] - ls[i << 1] + 1) * lz[i]; sum[i << 1 | 1] += 1ll * (rs[i << 1 | 1] - ls[i << 1 | 1] + 1) * lz[i]; lz[i] = 0; return ; } void build(int i,int l,int r) { ls[i] = l; rs[i] = r; if(l == r) { sum[i] = maxv[i] = minv[i] = read(); return ; } int mid = l + r >> 1; build(i << 1 , l , mid); build(i << 1 | 1 , mid + 1 , r); pushup(i); return ; } void modify(int i,int l,int r,int k) { if(ls[i] >= l&&rs[i] <= r) { sum[i] += 1ll * k * (rs[i] - ls[i] + 1); lz[i] += k; maxv[i] += k; minv[i] += k; return ; } if(ls[i] > r||rs[i] < l)return ; pushdown(i); int mid = ls[i] + rs[i] >> 1; if(l <= mid)modify(i << 1 , l , r , k); if(r > mid)modify(i << 1 | 1 , l , r , k); pushup(i); return ; } void Maintain(int i,int l,int r,int k) { if(ls[i] >= l&&rs[i] <= r) { int x = maxv[i] - (int)std::floor((double)(1.0 * (double)maxv[i] / (double)k)); int y = minv[i] - (int)std::floor((double)(1.0 * (double)minv[i] / (double)k)); if(x == y) { sum[i] -= 1ll * (rs[i] - ls[i] + 1) * x; lz[i] -= x; maxv[i] -= x; minv[i] -= x; return ; } } if(ls[i] > r||rs[i] < l)return ; pushdown(i); int mid = ls[i] + rs[i] >> 1; if(l <= mid)Maintain(i << 1 , l , r , k); if(r > mid)Maintain(i << 1 | 1 , l , r , k); pushup(i); return ; } ll Querymin(int i,int l,int r) { if(ls[i] >= l&&rs[i] <= r)return minv[i]; if(ls[i] > r||rs[i] < l)return INF; pushdown(i); int mid = ls[i] + rs[i] >> 1; ll s = INF; if(l <= mid)s = std::min(s , Querymin(i << 1 , l , r)); if(r > mid)s = std::min(s , Querymin(i << 1 | 1 , l , r)); return s; } ll Querysum(int i,int l,int r) { if(ls[i] >= l&&rs[i] <= r)return sum[i]; if(ls[i] > r||rs[i] < l)return 0; pushdown(i); int mid = ls[i] + rs[i] >> 1; ll s = 0; if(l <= mid)s += Querysum(i << 1 , l , r); if(r > mid)s += Querysum(i << 1 | 1 , l , r); return s; } int main() { n = read(); m = read(); build(1 , 1 , n); while(m --) { int op = read(),l = read() + 1,r = read() + 1,k; switch(op) { case 1: { k = read(); modify(1 , l , r , k); break ; } case 2: { k = read(); Maintain(1 , l , r , k); break ; } case 3: { printf("%lld\n",Querymin(1 , l , r)); break ; } case 4: { printf("%lld\n",Querysum(1 , l , r)); break ; } } } return 0; }
题目3846 [雅礼集训 2017 Day1] 市场
9
评论
2024-06-22 17:00:10
|