4889 字
13 分钟
(线段)树学是怎样炼成的
2025-01-20

我喜欢打线段树。

特别是动态开点线段树。每一次与线段树相见,我总觉得是受了上天的眷顾与恩惠。

那些动态开点线段树,114、514年地活在世上,那要经历多少、怎样的时光?

打线段树,线段树当然不会改变,但看打得久了,打线段树的人不由自主地有了变化——把自己看成了一棵线段树。

线段树不再是“他”,而在不知不觉中变成了“你”。

线段树已经成为独特的具体,有他的前世今生,成了相对于我的“你”。线段树也有自我意识吧,那样,我也可以妄想成为线段树的“你”,我和你!我用眼睛听你,用耳朵看你,用“心”换意念,去揣摩你。

想当年,不知是何人栽下了你,或者只是IOer遗落了一粒灵感、无心长成了你?1e114514199810谔谔年来,你经历了太多的朝代更迭,多少TLE、MLE,那都不算什么。

多少WA、TLE、OLE、MLE、UKE,你是怎么活下来的?此处在Luogu,古代应该人迹罕至、人为破坏不易。TLE因旁有n<=1e8,T掉你的机会减少;扎根处在宽阔的Luogu,MLE不能把你卡。

“高坡OJ上,尽是毒瘤翁。人人尽怀卡常意,不见AC映屏幕绿”,你是怎样幸运地一次次躲过了UKE之灾?

根仍在汲取,叶仍在吐纳,题面和数据一定是永不停歇地和你进行着交流互动。

“南方水阔,北地风多”,一年又一年风的OLE,都未能拿你奈何。

四周山峦逶迤,线段树木葱茏,long long 来过,线段树声哼吼,贯通肺腑。懂线段树的朋友说,这里一定有充足的评测资源,你四处探路的根须早已与CPU接通,至少1e114514199810谔谔年来从未枯竭。

古线段树从来不是孤独的,你一定不是单个的存在。

这里的线段树活得长,有风气、有遗传。在树状树祖直线距离不到两公里的地方还有一棵年龄相仿的线段树。大慨是在历史上受了unsigned long long,那棵线段树只有半个部分还活着,活着的半个部分倒也是生机盎然。

时间虽然已经过去了1e114514199810谔谔年,这样的情形似乎以后也不会改变。想象你们的未来,我有担忧,居然也有信心。


主要参考:lxl 课件

本文所讲的线段树题,仅包括思路重点在线段树的题,而不是用线段树优化什么。

其实硬要把线段树和扫描线分开是不可能的,这就是「我中有你,你中有我」。

因此这里例题放那边实际上是重点偏向哪边,并不是不用另一方的。

线段树#

标记信息的设计#

本节参考:

https://www.luogu.me/article/rqmfqvmu

https://www.cnblogs.com/Meatherm/p/17925813.html#:~

线段树基本原则(信息&标记):

  • 信息+信息->信息(区间合并pushup)
  • 标记+标记->标记(tag合并)
  • 标记+信息->信息(tag可对信息作用pushdown)

可以从维护信息出发,推出其他信息和标记。

例:小白

  • 单点加
  • 区间查最大子段和

由于查询,显然信息要维护最大子段和,于是考虑第一点。

一个大区间显然是由左右小区间合并上了,于是为了合并它,就需要再维护前缀最大值和后缀最大值。

由于是单点修改,并不需要标记。

以上可以写成模板,不用的线段树只有三个函数有差别。

#include <cstdio>
using namespace std;
struct Tag {
//标记h
Tag(){}
inline friend Tag operator + (Tag l , Tag r){
//信息合并(相当于pushup)
}
inline void operator += (Tag t){
(*this) = (*this) + t;
}
}tag[400005];
struct Seg {
//信息
inline friend Seg operator + (Seg ls , Seg rs){
//标记作用标记
}
inline void operator += (Tag t){
//标记作用信息
}
}tr[400005];
inline void pushdown(int p){
if(){ //判断标记非空
tr[p << 1] += tag[p]; tr[p << 1 | 1] += tag[p];
tag[p << 1] += tag[p]; tag[p << 1 | 1] += tag[p];
tag[p] = Tag();
}
}
void init(int p , int l , int r){
if(l == r){ return ; }
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
void upd(int p , int l , int r , int x , int y , Tag k){
if(l > y || r < x) return ;
if(l >= x && r <= y){ tr[p] += k; tag[p] += k; return ; }
int mid = (l + r) >> 1;
pushdown(p);
upd(p << 1 , l , mid , x , y , k);
upd(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) return tr[p];
int mid = (l + r) >> 1;
pushdown(p);
if(y <= mid) return query(p << 1 , l , mid , x , y);
if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}

从扫描线到历史版本和#

其实这边都是关于历史的。

一般的这种题,可以考虑将 l 做横轴,r 做纵轴,这样就成了矩形查询,区间修改,这玩意一般需要用到历史版本和。

我们很容易可以设计出信息和标记,但是问题就在于如何合并。

有的可能要考虑很多种情况,这里给出一种比较好用的方法:矩阵。

实际上可以把信息标记都放在矩阵上,那么就变成矩阵乘法。写出矩阵后,一般大多项都是固定没用的,于是可以把变的提出来,暴力解出转移,这样便有了标记的转移方式,这还可以尝试合并相同的值,大多数情况下这样跑出来的转移是和按照实际意义推的转移是相同的。

以下是暴力转移的代码(待办:用python写一个功能更完整的):

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n;
struct QWQ{ char a[105][105][10]; QWQ(){ memset(a , 0 , sizeof(a)); } }A , B;
inline void mul_max(QWQ x , QWQ y){ //M为负无穷
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++){
int flag = 0;
for(int k = n;k >= 1;k--)
if(x.a[i][k][1] != 'M' && y.a[k][j][1] != 'M'){ flag = k; break; }
if(!flag) continue;
printf("(%d %d)=max(" , i , j);
for(int k = 1;k <= n;k++){
if(x.a[i][k][1] == 'M' || y.a[k][j][1] == 'M') continue;
printf("%s+%s" , x.a[i][k] + 1 , y.a[k][j] + 1);
if(k != flag) putchar(',');
}
putchar(')'); putchar('\n');
}
}
inline void mul_(QWQ x , QWQ y){
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++){
int flag = 0;
for(int k = n;k >= 1;k--)
if(x.a[i][k][1] != '0' && y.a[k][j][1] != '0'){ flag = k; break; }
if(!flag) continue;
printf("ret.k[1] = ");
for(int k = 1;k <= n;k++){
if(x.a[i][k][1] == '0' || y.a[k][j][1] == '0') continue;
printf(" %s * %s" , x.a[i][k] + 1 , y.a[k][j] + 1);
if(k != flag) printf(" +");
}
printf(";\n");
}
}
int main(void){
scanf("%d" , &n);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
scanf("%s" , A.a[i][j] + 1);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
scanf("%s" , B.a[i][j] + 1);
mul_(A , B);
}

然后操作如果有涉及到 max 等的东西,可以考虑使用(max,+)(\max,+)矩阵,就是吧矩乘的×\times改成++++改成max\max

不过如果题目的操作过于复杂,其实没必要使用暴力转移找不变,可以直接矩乘,只是常数较大。

NOIP2022比赛#

看了好几遍题解,没看懂。。。(以下是自己想的,因为题解看不懂。。)

考虑第ii个点什么时候能对答案有贡献,显然,当且仅当区间包含ii,且满足l>Li,r<Ril > L_i,r < R_i,其中LiL_i表示第ii个点左边第一个大于aia_i的位置,RiR_i同理。于是这样,每个点都变成一个矩形:[Li+1,i,i,Ri1][L_i+1,i,i,R_i-1]

这样处理完,就有两个二维平面a,ba,b,答案就是[l,r,l,r][l,r,l,r]矩形内a×ba \times b的和。

但是。。。

扫描线怎么维护??

  • 矩形aa赋值。
  • 矩形bb赋值。
  • 求矩形a×ba \times b 的值。

???赋值怎么测回。

#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef unsigned long long ll;
int T , n , a[250005] , b[250005];
int la[250005] , ra[250005] , lb[250005] , rb[250005];
ll ans[250005];
struct Seg { ll s , xy , x , y; }tr[1000005];
struct Tag { ll tx , ty , adx , ady , adxy , adc; }tag[1000005];
inline void pushup(int p){ //信息+信息
tr[p].s = tr[p << 1].s + tr[p << 1 | 1].s;
tr[p].xy = tr[p << 1].xy + tr[p << 1 | 1].xy;
tr[p].x = tr[p << 1].x + tr[p << 1 | 1].x;
tr[p].y = tr[p << 1].y + tr[p << 1 | 1].y;
}
inline void tag_info(Seg &ret , Tag t , int le){ //标记作用信息
ret.s += ret.xy * t.adxy + ret.x * t.adx + ret.y * t.ady + le * t.adc;
if(t.tx && t.ty){
ret.xy = le * t.tx * t.ty;
ret.x = le * t.tx;
ret.y = le * t.ty;
}
else if(t.tx){
ret.xy = ret.y * t.tx;
ret.x = le * t.tx;
}
else if(t.ty){
ret.xy = ret.x * t.ty;
ret.y = le * t.ty;
}
}
inline void tag_tag(Tag &ret , Tag t){ //标记作用标记
if(ret.tx && ret.ty){
}
else if(ret.tx){
}
else if(ret.ty){
}
else{
ret.adx += t.adx;
ret.ady += t.ady;
ret.adxy += t.adxy;
ret.adc += t.adc;
}
if(t.tx) ret.tx = t.tx;
if(t.ty) ret.ty = t.ty;
}
struct Line {
int x , y1 , y2 , k , id;
bool operator < (const Line &b) const {
if(x == b.x){
if(id < 0 && b.id < 0) return k < b.k;
else return id > b.id;
}
return x < b.x;
}
}qwq[1000005]; int cnt;
inline void init(){
stack <pair <int , int> > tmp;
scanf("%d%d" , &T , &n);
for(int i = 1;i <= n;i++){
scanf("%d" , &a[i]);
while(!tmp.empty() && tmp.top().first < a[i]) tmp.pop();
if(!tmp.empty()) la[i] = tmp.top().second;
tmp.push(make_pair(a[i] , i));
}
while(!tmp.empty()) tmp.pop();
for(int i = 1;i <= n;i++){
scanf("%d" , &b[i]);
while(!tmp.empty() && tmp.top().first < b[i]) tmp.pop();
if(!tmp.empty()) lb[i] = tmp.top().second;
tmp.push(make_pair(b[i] , i));
}
for(int i = 1;i <= n;i++){
qwq[++cnt] = (Line){i , la[i] + 1 , i , a[i] , -1};
qwq[++cnt] = (Line){i , lb[i] + 1 , i , b[i] , -2};
}
scanf("%d" , &T);
for(int i = 1 , l , r;i <= T;i++){
scanf("%d%d" , &l , &r);
qwq[++cnt] = (Line){l , l , r , -1 , i};
qwq[++cnt] = (Line){r + 1 , l , r , 1 , i};
}
}
int main(void){
init();
// printf("qw%llu\n" , (ll)(-1) * query(1 , 1 , n , 1 , 2));
// tr[1].updad(1);
// upd2(1 , 1 , n , 1 , 1 , 1);
// upd1(1 , 1 , n , 1 , 1 , 2);
// tr[1].updad(1);
// printf("qw%llu\n" , query(1 , 1 , n , 1 , 2));
// upd2(1 , 1 , n , 1 , 2 , 2);
// upd1(1 , 1 , n , 2 , 2 , 1);
// tr[1].updad(1);
// printf("qw%llu\n" , (ll)(1) * query(1 , 1 , n , 1 , 2));
// return 0;
sort(qwq + 1 , qwq + 1 + cnt);
for(int i = 1;i <= cnt;i++){
printf("%d %d %d %d %d\n" , qwq[i].x , qwq[i].k , qwq[i].y1 , qwq[i].y2 , qwq[i].id);
if(qwq[i].x != qwq[i - 1].x) tr[1].updad(qwq[i].x - qwq[i - 1].x);
if(qwq[i].id < 0){
if(qwq[i].id == -1) upd1(1 , 1 , n , qwq[i].y1 , qwq[i].y2 , qwq[i].k);
if(qwq[i].id == -2) upd2(1 , 1 , n , qwq[i].y1 , qwq[i].y2 , qwq[i].k);
}
else{
ans[qwq[i].id] += (ll)qwq[i].k * query(1 , 1 , n , qwq[i].y1 , qwq[i].y2);
printf("%llu\n" , (ll)qwq[i].k * query(1 , 1 , n , qwq[i].y1 , qwq[i].y2));
}
}
for(int i = 1;i <= T;i++) printf("%llu\n" , ans[i]);
}

我已从 CPU 大成归来。

冯sir 说不需要撤回,于是问题变成:

aabb区间推平,求aibi\sum a_ib_i的历史和。

去掉历史和是平凡的,这里信息很容易看出来:aa的和,bb的和,abab的和,历史和(答案),区间长度。

以上记为x,y,xy,s,lex,y,xy,s,le

直接暴力矩阵!!!!!!!!!!!!!!!!!!

[xyxysle][0000001k000000000010k0001]=[kleykysle]\begin{bmatrix}x\\y\\xy\\s\\le \end{bmatrix} \begin{bmatrix}0&0&0&0&0\\0&1&k&0&0\\0&0&0&0&0\\0&0&0&1&0\\k&0&0&0&1 \end{bmatrix}=\begin{bmatrix}kle\\y\\ky\\s\\le \end{bmatrix}

另外一个推平同理。

[xyxysle][1000001000001100001000001]=[xleyxys+xyle]\begin{bmatrix}x\\y\\xy\\s\\le \end{bmatrix} \begin{bmatrix}1&0&0&0&0\\0&1&0&0&0\\0&0&1&1&0\\0&0&0&1&0\\0&0&0&0&1 \end{bmatrix}=\begin{bmatrix}xle\\y\\xy\\s+xy\\le \end{bmatrix}

然后这个是把答案加到历史和里的。

然后用上面的暴力程序多乘几次,发现除去1,01,0就只有1212种可能的值,我们维护它,然后再用暴力程序跑一遍,暴力跑出转移式子,直接复制上去即可。

#include <cstdio>
#include <algorithm>
#include <vector>
#include <stack>
using namespace std;
typedef unsigned long long ll;
struct Tag {
ll k[12];
inline Tag(){ k[0]=k[3]=k[6]=1;k[1]=k[2]=k[4]=k[5]=k[7]=k[8]=k[9]=k[10]=k[11]=0; }
inline Tag(int a0 , int a1 , int a2 , int a3 , int a4 , int a5 , int a6 , int a7 , int a8 , int a9 , int a10 , int a11){k[0]=a0;k[1]=a1;k[2]=a2;k[3]=a3;k[4]=a4;k[5]=a5;k[6]=a6;k[7]=a7;k[8]=a8;k[9]=a9;k[10]=a10;k[11]=a11;}
inline Tag friend operator + (Tag l , Tag r) {
Tag ret;
ret.k[0] = l.k[0] * r.k[0];
ret.k[1] = l.k[0] * r.k[1] + l.k[1] * r.k[6];
ret.k[2] = l.k[0] * r.k[2] + l.k[1] * r.k[7] + l.k[2];
ret.k[3] = l.k[3] * r.k[3];
ret.k[4] = l.k[3] * r.k[4] + l.k[4] * r.k[6];
ret.k[5] = l.k[3] * r.k[5] + l.k[4] * r.k[7] + l.k[5];
ret.k[6] = l.k[6] * r.k[6];
ret.k[7] = l.k[6] * r.k[7] + l.k[7];
ret.k[8] = l.k[8] * r.k[0] + r.k[8];
ret.k[9] = l.k[9] * r.k[3] + r.k[9];
ret.k[10] = l.k[8] * r.k[1] + l.k[9] * r.k[4] + l.k[10] * r.k[6] + r.k[10];
ret.k[11] = l.k[8] * r.k[2] + l.k[9] * r.k[5] + l.k[10] * r.k[7] + l.k[11] + r.k[11];
return ret;
}
inline void operator += (Tag x) { (*this) = (*this) + x; }
inline bool zbj(){ return k[0]!=1||k[3]!=1||k[6]!=1||k[1]||k[2]||k[4]||k[5]||k[7]||k[8]||k[9]||k[10]||k[11]; }
}lazy[1000005];
struct Seg {
ll x , y , xy , s , le;
inline Seg friend operator + (Seg l , Seg r){
return (Seg){
l.x + r.x , l.y + r.y , l.xy + r.xy ,
l.s + r.s , l.le + r.le
};
}
inline void operator += (Tag p){
(*this) = (Seg){
x * p.k[0] + le * p.k[8] ,
y * p.k[3] + le * p.k[9] ,
x * p.k[1] + y * p.k[4] + xy * p.k[6] + le * p.k[10] ,
x * p.k[2] + y * p.k[5] + xy * p.k[7] + le * p.k[11] + s ,
le
};
}
}tr[1000005];
void init(int p , int l , int r){
tr[p].le = r - l + 1;
if(l == r) return ;
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
}
inline void pushdown(int p){
if(lazy[p].zbj()){
tr[p << 1] += lazy[p]; tr[p << 1 | 1] += lazy[p];
lazy[p << 1] += lazy[p]; lazy[p << 1 | 1] += lazy[p];
lazy[p] = Tag();
}
}
void upd(int p , int l , int r , int x , int y , Tag k){
if(l > y || r < x) return ;
if(l >= x && r <= y){ tr[p] += k; lazy[p] += k; return ; }
int mid = (l + r) >> 1;
pushdown(p);
upd(p << 1 , l , mid , x , y , k);
upd(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) return tr[p];
int mid = (l + r) >> 1;
pushdown(p);
if(y <= mid) return query(p << 1 , l , mid , x , y);
if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}
//void print(int p , int l , int r){
// printf("(%d %d %d)%llu %llu\n" , p , l , r , tr[p].xy , tr[p].s);
// if(l == r) return ;
// int mid = (l + r) >> 1;
// pushdown(p);
// print(p << 1 , l , mid); print(p << 1 | 1 , mid + 1 , r);
// tr[p] = tr[p << 1] + tr[p << 1 | 1];
//}
int T , n , a[250005] , b[250005] , la[250005] , lb[250005];
struct QWQ { int l , r , k , op; };
vector <QWQ> chm[250005] , qqq[250005];
ll ans[250005];
int main(void){
scanf("%d%d" , &T , &n);
init(1 , 1 , n);
stack <pair <int , int> > tmp;
for(int i = 1;i <= n;i++){
scanf("%d" , &a[i]);
while(!tmp.empty() && tmp.top().first < a[i]) tmp.pop();
if(!tmp.empty()) la[i] = tmp.top().second;
tmp.push(make_pair(a[i] , i));
}
while(!tmp.empty()) tmp.pop();
for(int i = 1;i <= n;i++){
scanf("%d" , &b[i]);
while(!tmp.empty() && tmp.top().first < b[i]) tmp.pop();
if(!tmp.empty()) lb[i] = tmp.top().second;
tmp.push(make_pair(b[i] , i));
}
for(int i = 1;i <= n;i++){
chm[i].push_back((QWQ){la[i] + 1 , i , a[i] , 0});
chm[i].push_back((QWQ){lb[i] + 1 , i , b[i] , 1});
}
scanf("%d" , &T);
for(int i = 1 , l , r;i <= T;i++){
scanf("%d%d" , &l , &r);
qqq[l - 1].push_back((QWQ){l , r , -1 , i});
qqq[r].push_back((QWQ){l , r , 1 , i});
}
for(int i = 1;i <= n;i++){
for(QWQ j : chm[i]){
if(j.op) upd(1 , 1 , n , j.l , j.r , Tag(1 , j.k , 0 , 0 , 0 , 0 , 0 , 0 , 0 , j.k , 0 , 0));
if(!j.op)upd(1 , 1 , n , j.l , j.r , Tag(0 , 0 , 0 , 1 , j.k , 0 , 0 , 0 , j.k , 0 , 0 , 0));
}
// printf("%llu %llu\n" , tr[1].xy , tr[1].s);
upd(1 , 1 , n , 1 , n , Tag(1 , 0 , 0 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0));
// printf("%llu %llu\n" , tr[1].xy , tr[1].s);
// print(1 , 1 , n);
for(QWQ j : qqq[i])
ans[j.op] += j.k * query(1 , 1 , n , j.l , j.r).s;
}
for(int i = 1;i <= T;i++) printf("%llu\n" , ans[i]);
}
//inline Tag(ll a1 , ll a2 , ll a4 , ll a5 , ll a7 , ll a8): k11(a1) , k12(a2) , k21(a4) , k22(a5) , k31(a7) , k32(a8

P3246 序列#

求区间内所有子区间的最小值的和

我发现类似这种子区间的最值可以按如下思路思考:

比如有 5,2,4,1,35,2,4,1 ,3,那么:

5 2 4 1 3 5 2 2 1 1

2 4 1 3 2 2 1 1

4 1 3 -----> 4 1 1
1 3 1 1
3 3

可以发现是一个矩形,扫描线,且不需要撤回,后边的矩形会直接覆盖前面的。

然后就可以考虑每个点在对什么区间是有贡献的。

pre,nxtpre,nxt意思显然

因此每个矩形就是[pre+1,i,i,nxt1][pre + 1 , i , i , nxt - 1]

由于是直接覆盖的,nxt 不需要考虑

pre 用单调栈即可,后面的用扫描线离线做。

于是问题变成:

a区间推平,求历史区间的和

历史和只会用矩阵。信息很容易知道,就是区间和ss,区间历史和bb,于是:

[sble][000010k01]=[k×leble]\begin{bmatrix}s\\b \\ le\end{bmatrix}\begin{bmatrix}0&0&0\\0&1&0 \\ k&0&1\end{bmatrix}=\begin{bmatrix}k\times le\\b \\ le\end{bmatrix}

[sble][110010001]=[sb+sle]\begin{bmatrix}s\\b \\ le\end{bmatrix}\begin{bmatrix}1&1&0\\0&1&0 \\ 0&0&1\end{bmatrix}=\begin{bmatrix}s\\b+s \\ le\end{bmatrix}

用暴力矩乘得出只有四个位置是有值的。直接再跑一次得转移即可。

#include <cstdio>
#include <algorithm>
#include <stack>
#include <vector>
using namespace std;
typedef long long ll;
struct Tag {
ll k1 , k2 , k3 , k4;
inline Tag(){k1 = 1; k2 = k3 = k4 = 0; }
inline Tag(int _ , int __ , int ___ , int ____): k1(_) , k2(__) , k3(___) , k4(____) {}
inline friend Tag operator + (Tag l , Tag r){
Tag ret;
ret.k1 = l.k1 * r.k1;
ret.k2 = l.k1 * r.k2 + l.k2;
ret.k3 = l.k3 * r.k1 + r.k3;
ret.k4 = l.k3 * r.k2 + l.k4 + r.k4;
return ret;
}
inline void operator += (Tag x){
(*this) = (*this) + x;
}
}lz[400005];
struct Seg {
ll s , b; int le;
inline Seg(){ s = b = le = 0; }
inline friend Seg operator + (Seg l , Seg r){
Seg ret;
ret.s = l.s + r.s;
ret.b = l.b + r.b;
ret.le = l.le + r.le;
return ret;
}
inline void operator += (Tag x){
b += x.k2 * s + x.k4 * le;
s = x.k1 * s + x.k3 * le;
}
}tr[400005];
inline void upd(int p , Tag x){ tr[p] += x; lz[p] += x; }
inline void pushdown(int p){
if(lz[p].k1 == 1 && lz[p].k2 == 0 && lz[p].k3 == 0 && lz[p].k4 == 0) return ;
upd(p << 1 , lz[p]);
upd(p << 1 | 1 , lz[p]);
lz[p] = (Tag){1 , 0 , 0 , 0};
}
void upd(int p , int l , int r , int x , int y , Tag k){
if(l > y || r < x) return ;
if(l >= x && r <= y) { upd(p , k); return ; }
int mid = (l + r) >> 1;
pushdown(p);
upd(p << 1 , l , mid , x , y , k);
upd(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) return tr[p];
int mid = (l + r) >> 1;
pushdown(p);
if(y <= mid) return query(p << 1 , l , mid , x , y);
if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}
void init(int p , int l , int r){
if(l == r){ tr[p].s = tr[p].b = 0; tr[p].le = 1; return ;}
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
int n , q , a[100005] , pre[100005];
struct QWQ { int l , r , k , op; };
vector <QWQ> uuu[200005] , qqq[100005];
ll ans[100005];
void solve(){
init(1 , 1 , n);
stack <pair <int , int> > tmp;
for(int i = 1;i <= n;i++){
while(!tmp.empty() && tmp.top().first > a[i]) tmp.pop();
if(!tmp.empty()) pre[i] = tmp.top().second;
tmp.push({a[i] , i});
}
for(int i = 1;i <= n;i++)
uuu[i].push_back((QWQ){pre[i] + 1 , i , a[i] , 0});
for(int i = 1;i <= q;i++){
int l , r; scanf("%d%d" , &l , &r);
qqq[l - 1].push_back((QWQ){l , r , -1 , i});
qqq[r].push_back((QWQ){l , r , 1 , i});
}
for(int i = 1;i <= n;i++){
for(QWQ j : uuu[i]) upd(1 , 1 , n , j.l , j.r , Tag(0 , 0 , j.k , 0));
upd(1 , Tag(1 , 1 , 0 , 0));
for(QWQ j : qqq[i])
ans[j.op] += 1LL * j.k * query(1 , 1 , n , j.l , j.r).b;
}
for(int i = 1;i <= q;i++) printf("%lld\n" , ans[i]);
}
int main(void){
scanf("%d%d" , &n , &q);
for(int i = 1;i <= n;i++) scanf("%d" , &a[i]);
solve();
}

CF997E & 猫猫贴贴#

给定一个排列,多次询问,求区间内子区间有多少是取值连续的 n,q1.2×105n,q\le 1.2 \times 10^5

毒瘤题,十年河东十年河西,莫欺少年穷。

首先排列很重要,意味着取值不重复,于是取值连续的区间需要满足maxmin=rl\max-\min=r-l,即maxminr+l=0\max-\min-r+l=0

按照扫描线的思路,放到二维平面上,横 l,纵 r,先把每个点权值改为lrl-r,这个是简单的,可以转换成区间加。

然后考虑更新min\minmax\max,如果可以问题就变成矩形求00的个数,因为0maxminr+l0\le \max-\min-r+l,所以这玩意可以用最小值和最小值的个数维护。

#include <cstdio>
#include <vector>
#include <algorithm>
#include <stack>
using namespace std;
struct Tag {
int ad , tg;
inline Tag(){ ad = tg = 0; }
inline Tag(int _ , int __): ad(_) , tg(__) {}
inline void operator += (Tag b){ ad += b.ad; tg += b.tg; }
}lz[480005];
struct Seg {
int mi , cntmi; long long ans;
inline friend Seg operator + (Seg ls , Seg rs){
Seg ret;
if(ls.mi < rs.mi){
ret.mi = ls.mi;
ret.cntmi = ls.cntmi;
}
else if(ls.mi > rs.mi){
ret.mi = rs.mi;
ret.cntmi = rs.cntmi;
}
else{
ret.mi = ls.mi;
ret.cntmi = ls.cntmi + rs.cntmi;
}
ret.ans = ls.ans + rs.ans;
return ret;
}
// inline void operator += (Tag x){
// mi += x.ad;
// ans += 1LL * cntmi * x.tg;
// }
}tr[480005];
inline void upd(int p , Tag x , int tmp){
lz[p].ad += x.ad;
if((tr[p].mi += x.ad) == tmp){
tr[p].ans += 1LL * x.tg * tr[p].cntmi;
lz[p].tg += x.tg;
}
}
inline void pushdown(int p){
if(lz[p].ad == 0 && lz[p].tg == 0) return ;
upd(p << 1 , lz[p] , tr[p].mi);
upd(p << 1 | 1 , lz[p] , tr[p].mi);
lz[p] = Tag(0 , 0);
}
void upd(int p , int l , int r , int x , int y , Tag k){
if(l > y || r < x) return ;
if(l >= x && r <= y){ upd(p , k , 0); return ; }
int mid = (l + r) >> 1;
pushdown(p);
upd(p << 1 , l , mid , x , y , k);
upd(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) return tr[p];
int mid = (l + r) >> 1;
pushdown(p);
if(y <= mid) return query(p << 1, l , mid , x , y);
if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}
void init(int p , int l , int r){
if(l == r){ tr[p].cntmi = 1; return ; }
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
int n , m , p[120005];
int lx[120005] , rx[120005] , li[120005] , ri[120005];
struct QWQ { int l , r , k , id; };
vector <QWQ> u[120005] , q[120005];
inline void solve(int retl[120005] , int retr[120005] , bool (*cmp)(int , int)){
stack <pair <int , int> > st;
for(int i = 1;i <= n;i++){
while(!st.empty() && cmp(st.top().first , p[i])) st.pop();
retl[i] = st.empty()? 0 : st.top().second;
st.push({p[i] , i});
}
while(!st.empty()) st.pop();
for(int i = n;i >= 1;i--){
while(!st.empty() && cmp(st.top().first , p[i])) st.pop();
retr[i] = st.empty()? n + 1 : st.top().second;
st.push({p[i] , i});
}
}
long long ans[120005];
int main(void){
scanf("%d" , &n);
for(int i = 1;i <= n;i++) scanf("%d" , &p[i]);
scanf("%d" , &m);
for(int i = 1;i <= m;i++){
int l , r; scanf("%d%d" , &l , &r);
q[l - 1].push_back((QWQ){l , r , -1 , i});
q[r].push_back((QWQ){l , r , 1 , i});
}
solve(lx , rx , [](int x , int y) -> bool {return x < y; });
solve(li , ri , [](int x , int y) -> bool {return x > y; });
for(int i = 1;i <= n;i++){
u[lx[i] + 1].push_back((QWQ){i , rx[i] - 1 , p[i] , 0});
u[i + 1].push_back((QWQ){i , rx[i] - 1 , -p[i] , 0});
u[li[i] + 1].push_back((QWQ){i , ri[i] - 1 , -p[i] , 0});
u[i + 1].push_back((QWQ){i , ri[i] - 1 , p[i] , 0});
}
for(int i = 1;i <= n;i++){
u[i].push_back((QWQ){1 , n , 1 , 0});
u[1].push_back((QWQ){i , i , -i , 0});
}
init(1 , 1 , n);
for(int i = 1;i <= n;i++){
for(QWQ j : u[i]) upd(1 , 1 , n , j.l , j.r , Tag(j.k , 0));
upd(1 , Tag(0 , 1) , 0);
for(QWQ j : q[i]) ans[j.id] += 1LL * j.k * query(1 , 1 , n , j.l , j.r).ans;
}
for(int i = 1;i <= m;i++) printf("%lld\n" , ans[i]);
}

P9990 Ynoi Easy Round 2023 TEST_90 & P10822#

多次询问区间的所有子区间有多少满足出现数字个数是奇数

首先可以转换成区间内所有子区间的数字个数对 2 取模的和。

一般的,区间出现个数可以用[prei<l]\sum [pre_i < l]来计算,根据这个我们很容易写出矩形:[prei+1,i,i,n][pre_i+1,i,i,n]

发现区间增加 1 实际上就是异或 1,于是需要线段树实现区间异或,求历史区间和。

有点困难,考虑用矩阵思考下。

维护的显然就是区间 1 的数量aa,历史区间和bb。于是:

[able][100010101]=[leable]\begin{bmatrix}a\\b \\ le\end{bmatrix}\begin{bmatrix}-1&0&0\\0&1&0 \\ 1&0&1\end{bmatrix}=\begin{bmatrix}le-a\\b \\ le\end{bmatrix}

[able][110010001]=[aa+ble]\begin{bmatrix}a\\b \\ le\end{bmatrix}\begin{bmatrix}1&1&0\\0&1&0 \\ 0&0&1\end{bmatrix}=\begin{bmatrix}a\\a+b \\ le\end{bmatrix}

又到了暴力转移的时候。

这方法常数有点大。。。。要卡常(最好不用vector)

#include <cstdio>
#include <algorithm>
using namespace std;
char buf[1 << 21] , *p1 , *p2;
#define GC p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2) ? EOF : *p1++
inline int read() {
int x = 0; char ch = GC;
while(ch > '9' || ch < '0') ch = GC;
while(ch >= '0' && ch <= '9'){ x = (x << 1) + (x << 3) + (ch ^ 48); ch = GC; }
return x;
}
struct Tag {
int k1 , k3 , k2 , k4;
inline Tag(){ k1 = 1; k2 = k3 = k4 = 0; }
inline Tag(int _ , int __ , int ___ , int ____): k1(_) , k2(__) , k3(___) , k4(____) {}
inline friend Tag operator + (Tag l , Tag r){
return Tag(
l.k1 * r.k1 ,
l.k1 * r.k2 + l.k2 ,
l.k3 * r.k1 + r.k3 ,
l.k3 * r.k2 + l.k4 + r.k4
);
}
inline void operator += (Tag x){ (*this) = (*this) + x; }
}lz[4000005];
struct Seg {
int a , le; long long b;
inline Seg(){ a = b = le = 0; }
inline Seg(int _ , long long __ , int ___): a(_) , b(__) , le(___) {}
inline friend Seg operator + (Seg ls , Seg rs){
return Seg(ls.a + rs.a , ls.b + rs.b , ls.le + rs.le);
}
inline void operator += (Tag x){
(*this) = Seg(x.k1 * a + x.k3 * le , 1LL * x.k2 * a + b + 1LL * x.k4 * le , le);
}
}tr[4000004];
inline void pushdown(int p){
if(lz[p].k1 == 1 && lz[p].k2 == 0 && lz[p].k3 == 0 && lz[p].k4 == 0) return ;
tr[p << 1] += lz[p]; lz[p << 1] += lz[p];
tr[p << 1 | 1] += lz[p]; lz[p << 1 | 1] += lz[p];
lz[p] = Tag();
}
void upd(int p , int l , int r , int x , int y , Tag k){
if(l > y || r < x) return ;
if(l >= x && r <= y){
tr[p] += k; lz[p] += k;
return ;
}
int mid = (l + r) >> 1;
pushdown(p);
upd(p << 1 , l , mid , x , y , k);
upd(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) return tr[p];
int mid = (l + r) >> 1;
pushdown(p);
if(y <= mid) return query(p << 1 , l , mid , x , y);
if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}
void init(int p , int l , int r){ //
if(l == r){ tr[p].le = 1; return ; }
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
int n , m , pre[1000005];
struct QWQ {
int x , l , r , k , id;
inline bool operator < (QWQ b) {
if(x == b.x) return id < b.id;
return x < b.x;
}
}qqq[4000005]; int qn;
long long ans[1000005];
int main(void){
n = read();
for(int i = 1 , a;i <= n;i++){
a = read();
qqq[i] = (QWQ){pre[a] + 1 , i , n , 0 , 0};
qqq[i + n] = (QWQ){i + 1 , i , n , 0 , 0};
pre[a] = i;
}
m = read();
qn = (n << 1);
for(int i = 1;i <= m;i++){
int l , r; l = read(); r = read();
qqq[++qn] = (QWQ){l - 1 , l , r , 0 , i};
qqq[++qn] = (QWQ){r , l , r , 1 , i};
}
sort(qqq + 1 , qqq + 1 + qn);
init(1 , 1 , n);
int i = 1;
while(i <= qn){
int st = qqq[i].x;
while(i <= qn && qqq[i].x == st && qqq[i].id == 0)
upd(1 , 1 , n , qqq[i].l , qqq[i].r , Tag(-1 , 0 , 1 , 0)) , i++;
tr[1] += Tag(1 , 1 , 0 , 0); lz[1] += Tag(1 , 1 , 0 , 0);
while(i <= qn && qqq[i].x == st)
ans[qqq[i].id] += qqq[i].k? query(1 , 1 , n , qqq[i].l , qqq[i].r).b : -query(1 , 1 , n , qqq[i].l , qqq[i].r).b , i++;
}
for(int i = 1;i <= m;i++) printf("%lld\n" , ans[i]);
}

区间最值操作与历史最值问题#

本节参考:

论文《区间最值操作与历史最值问题》——吉如一

区间最值操作与区间历史最值详解 灵梦

oi-wki

区间最值操作#

实现区间取最值的一种方法。(下以取最小值为例)

区间维护区间最大值(mx),严格次大值(mx2),最大值的个数(cnt)。

然后区间操作(ai=min(x,ai)a_i=\min(x,a_i))有如下情况:

  • mxxmx \le x:直接退出
  • mx2<xmxmx2 < x \le mx:仅修改最大值,打标记。
  • xmx2x\le mx2:递归下去。

论文证明复杂度为O(logn)O(\log n)

然后如果加上区间加的操作,实现类似,复杂度变为O(log2n)O(\log^2 n)


深挖上面那种方法的本质,实际上就是分类

将点分成两类:最大值和非最大值。

于是另一种实现区间取最值的方法呼吁而出。

仅修改最大值,那么就是推平操作,可以转换成加操作。

标记需要两套:最大的加标记,非最大值的加标记。

于是这种方法便实现了:区间取最值->区间加

这种思路很有启发性。

最佳女选手#

区间加,区间取最大值,区间取最小值,求区间和,求区间最大最小

类比:如果同时有取最大和取最小的操作,那么可以分三类。最大,最小,其他。

按照上面方法维护即可,注意合并函数里的细节。

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll inf = 1e17;
char buf[1 << 21] , *p1 , *p2;
#define GC p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2) ? EOF : *p1++
inline int read() {
int x = 0;
bool f = false;
char ch = GC;
while(ch > '9' || ch < '0'){
if(ch == '-')
f = true;
ch = GC;
}
while(ch >= '0' && ch <= '9'){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = GC;
}
return f? -x : x;
}
struct Tag {
ll admx , admi , ad;
inline void operator += (Tag b){ admx += b.admx; admi += b.admi; ad += b.ad; }
}tg[2000005];
struct Seg {
ll mx , mx2; int cntmx;
ll mi , mi2; int cntmi;
ll sum; int le;
inline friend Seg operator + (Seg ls , Seg rs){
Seg ret;
if(ls.mx > rs.mx){
ret.mx = ls.mx; ret.cntmx = ls.cntmx;
ret.mx2 = max(ls.mx2 , rs.mx);
}
else if(ls.mx < rs.mx){
ret.mx = rs.mx; ret.cntmx = rs.cntmx;
ret.mx2 = max(ls.mx , rs.mx2);
}
else{
ret.mx = ls.mx; ret.cntmx = ls.cntmx + rs.cntmx;
ret.mx2 = max(ls.mx2 , rs.mx2);
}
if(ls.mi < rs.mi){
ret.mi = ls.mi; ret.cntmi = ls.cntmi;
ret.mi2 = min(ls.mi2 , rs.mi);
}
else if(ls.mi > rs.mi){
ret.mi = rs.mi; ret.cntmi = rs.cntmi;
ret.mi2 = min(ls.mi , rs.mi2);
}
else {
ret.mi = ls.mi; ret.cntmi = ls.cntmi + rs.cntmi;
ret.mi2 = min(ls.mi2 , rs.mi2);
}
ret.sum = ls.sum + rs.sum;
ret.le = ls.le + rs.le;
return ret;
}
inline void operator += (Tag x){
if(mx == mi) sum += x.admx * cntmx;
else sum += x.admx * cntmx + x.admi * cntmi + x.ad * (le - cntmx - cntmi);
if(mi2 == mx) mi2 += x.admx;
else if(mi2 != inf) mi2 += x.ad;
if(mx2 == mi) mx2 += x.admi;
else if(mx2 != -inf) mx2 += x.ad;
mx += x.admx; mi += x.admi;
}
}tr[2000005];
inline void upd(int p , Tag x){
if(tr[p].mx == tr[p].mi){
if(x.admi == x.ad) x.admi = x.admx;
else x.admx = x.admi;
}
tr[p] += x; tg[p] += x;
}
inline void pushdown(int p){
if(tg[p].ad == 0 && tg[p].admx == 0 && tg[p].admi == 0) return ;
ll mx = max(tr[p << 1].mx , tr[p << 1 | 1].mx);
ll mi = min(tr[p << 1].mi , tr[p << 1 | 1].mi);
upd(p << 1 , (Tag){
tr[p << 1].mx == mx? tg[p].admx : tg[p].ad ,
tr[p << 1].mi == mi? tg[p].admi : tg[p].ad ,
tg[p].ad
});
upd(p << 1 | 1 , (Tag){
tr[p << 1 | 1].mx == mx? tg[p].admx : tg[p].ad ,
tr[p << 1 | 1].mi == mi? tg[p].admi : tg[p].ad ,
tg[p].ad
});
tg[p] = (Tag){0 , 0 , 0};
}
int a[500005];
void init(int p , int l , int r){
if(l == r){
tr[p].mx2 = -inf; tr[p].mi2 = inf;
tr[p].sum = tr[p].mx = tr[p].mi = a[l];
tr[p].cntmx = tr[p].cntmi = 1;
tr[p].le = 1;
return ;
}
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
void upd1(int p , int l , int r , int x , int y , Tag k){
if(l > y || r < x) return ;
if(l >= x && r <= y){ upd(p , k); return ; }
pushdown(p);
int mid = (l + r) >> 1;
upd1(p << 1 , l , mid , x , y , k);
upd1(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
void upd_max(int p , int l , int r , int x , int y , int k){
if(l > y || r < x || tr[p].mi >= k) return ;
if(l >= x && r <= y && k < tr[p].mi2){
upd(p , (Tag){0 , k - tr[p].mi , 0});
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
upd_max(p << 1 , l , mid , x , y , k);
upd_max(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
void upd_min(int p , int l , int r , int x , int y , int k){
if(l > y || r < x || tr[p].mx <= k) return ;
if(l >= x && r <= y && k > tr[p].mx2){
upd(p , (Tag){k - tr[p].mx , 0 , 0});
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
upd_min(p << 1 , l , mid , x , y , k);
upd_min(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) return tr[p];
pushdown(p);
int mid = (l + r) >> 1;
if(y <= mid) return query(p << 1 , l , mid , x , y);
if(x > mid ) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}
int n , m;
int main(void){
n = read();
for(int i = 1;i <= n;i++) a[i] = read();
init(1 , 1 , n);
m = read();
while(m--){
int op , l , r;
op = read(); l = read(); r = read();
if(op == 1){
int x = read();
upd1(1 , 1 , n , l , r , (Tag){x , x , x});
}
if(op == 2){
int x = read();
upd_max(1 , 1 , n , l , r , x);
}
if(op == 3){
int x = read();
upd_min(1 , 1 , n , l , r , x);
}
if(op == 4) printf("%lld\n" , query(1 , 1 , n , l , r).sum);
if(op == 5) printf("%lld\n" , query(1 , 1 , n , l , r).mx);
if(op == 6) printf("%lld\n" , query(1 , 1 , n , l , r).mi);
}
}

盟誓的文艺复兴#

this

区间取最值,区间求有几个子区间的最大值等于大区间的最大值。

我之前出的,很水,其实重点在三操作,不过三操作也简单。不说。

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
int id , T;
int a[100005];
int n , m;
const int inf = 0x7fffffff;
struct Tree {
int maxn , maxsum , secondmax;
int minn , secondmin;
pair <int , int > mintag , maxtag;
ll twt;
int maxl , maxr;
inline void min_upd(pair <int , int> t){
if(t.first < maxn){
if(secondmin == maxn)
secondmin = t.first;
maxn = t.first;
if(secondmax == -inf)
minn = t.first;
mintag = t;
}
}
inline void max_upd(pair <int , int> t){
if(t.first > minn){
if(secondmax == minn)
secondmax = t.first;
minn = t.first;
if(secondmin == inf)
maxn = t.first;
maxtag = t;
}
}
}qwq[400005];
inline ll square(int x){ return 1LL * x * x; }
inline Tree merge(int l , int r , int mid , Tree ls , Tree rs){
Tree ret;
ret.maxtag = ret.mintag = {-inf , 0};
if(ls.maxn > rs.maxn){
ret.maxn = ls.maxn;
ret.maxsum = ls.maxsum;
ret.secondmax = max(ls.secondmax , rs.maxn);
ret.maxl = ls.maxl;
ret.maxr = ls.maxr;
ret.twt = (
ls.twt - square(mid - ls.maxr) +
square(r - ls.maxr)
);
}
if(ls.maxn < rs.maxn){
ret.maxn = rs.maxn;
ret.maxsum = rs.maxsum;
ret.secondmax = max(rs.secondmax , ls.maxn);
ret.maxl = rs.maxl;
ret.maxr = rs.maxr;
ret.twt = (
rs.twt - square(rs.maxl - mid - 1) +
square(rs.maxl - l)
);
}
if(ls.maxn == rs.maxn){
ret.maxn = ls.maxn;
ret.maxsum = ls.maxsum + rs.maxsum;
ret.secondmax = max(ls.secondmax , rs.secondmax);
ret.maxl = ls.maxl;
ret.maxr = rs.maxr;
ret.twt = (
(ls.twt - square(mid - ls.maxr)) +
(rs.twt - square(rs.maxl - mid - 1)) +
square(rs.maxl - ls.maxr - 1)
);
}
if(ls.minn < rs.minn){
ret.minn = ls.minn;
ret.secondmin = min(ls.secondmin , rs.minn);
}
if(ls.minn > rs.minn){
ret.minn = rs.minn;
ret.secondmin = min(rs.secondmin , ls.minn);
}
if(ls.minn == rs.minn){
ret.minn = ls.minn;
ret.secondmin = min(ls.secondmin , rs.secondmin);
}
return ret;
}
inline void pushdown(int p){
if(qwq[p].mintag.first != -inf && qwq[p].maxtag.first != -inf){
if(qwq[p].mintag.second < qwq[p].maxtag.second){
qwq[p << 1].max_upd(qwq[p].maxtag);
qwq[p << 1 | 1].max_upd(qwq[p].maxtag);
qwq[p].maxtag = {-inf , 0};
qwq[p << 1].min_upd(qwq[p].mintag);
qwq[p << 1 | 1].min_upd(qwq[p].mintag);
qwq[p].mintag = {-inf , 0};
}
else{
qwq[p << 1].min_upd(qwq[p].mintag);
qwq[p << 1 | 1].min_upd(qwq[p].mintag);
qwq[p].mintag = {-inf , 0};
qwq[p << 1].max_upd(qwq[p].maxtag);
qwq[p << 1 | 1].max_upd(qwq[p].maxtag);
qwq[p].maxtag = {-inf , 0};
}
return ;
}
if(qwq[p].mintag.first != -inf){
qwq[p << 1].min_upd(qwq[p].mintag);
qwq[p << 1 | 1].min_upd(qwq[p].mintag);
qwq[p].mintag = {-inf , 0};
}
if(qwq[p].maxtag.first != -inf){
qwq[p << 1].max_upd(qwq[p].maxtag);
qwq[p << 1 | 1].max_upd(qwq[p].maxtag);
qwq[p].maxtag = {-inf , 0};
}
}
void init(int p , int l , int r){
if(l == r){
qwq[p].maxtag = qwq[p].mintag = {-inf , 0};
qwq[p].maxn = qwq[p].minn = a[l];
qwq[p].maxsum = 1;
qwq[p].secondmax = -inf;
qwq[p].secondmin = inf;
qwq[p].maxl = qwq[p].maxr = l;
qwq[p].twt = 0;
return ;
}
int mid = (l + r) >> 1;
init(p << 1 , l , mid);
init(p << 1 | 1 , mid + 1 , r);
qwq[p] = merge(l , r , mid , qwq[p << 1] , qwq[p << 1 | 1]);
}
void minupd(int p , int l , int r , int x , int y , int k){
if(l > y || r < x)
return ;
if(k >= qwq[p].maxn)
return ;
if(l >= x && r <= y && k > qwq[p].secondmax){ //只改最�?
if(qwq[p].maxn == qwq[p].secondmin)
qwq[p].secondmin = k;
qwq[p].maxn = k;
if(qwq[p].secondmax == -inf)
qwq[p].minn = k;
qwq[p].mintag = {k , m};
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
minupd(p << 1 , l , mid , x , y , k);
minupd(p << 1 | 1 , mid + 1 , r , x , y , k);
qwq[p] = merge(l , r , mid , qwq[p << 1] , qwq[p << 1 | 1]);
}
void maxupd(int p , int l , int r , int x , int y , int k){
if(l > y || r < x)
return ;
if(k <= qwq[p].minn)
return ;
if(l >= x && r <= y && k < qwq[p].secondmin){
if(qwq[p].minn == qwq[p].secondmax)
qwq[p].secondmax = k;
qwq[p].minn = k;
if(qwq[p].secondmin == inf)
qwq[p].maxn = k;
qwq[p].maxtag = {k , m};
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
maxupd(p << 1 , l , mid , x , y , k);
maxupd(p << 1 | 1 , mid + 1 , r , x , y , k);
qwq[p] = merge(l , r , mid , qwq[p << 1] , qwq[p << 1 | 1]);
}
Tree solve3(int p , int l , int r , int x , int y){
if(l > y || r < x)
return (Tree){0 , 0 , 0 , 0 , 0 , {0 , 0} , {0 , 0} , 0 , 0 , 0};
// printf("twt%lld %d %d\n" , qwq[p].twt , l , r);
if(l >= x && r <= y)
return qwq[p];
int mid = (l + r) >> 1;
pushdown(p);
Tree ls = solve3(p << 1 , l , mid , x , y);
Tree rs = solve3(p << 1 | 1 , mid + 1 , r , x , y);
if(ls.maxsum == 0)
return rs;
if(rs.maxsum == 0)
return ls;
return merge(max(x , l) , min(y , r) , mid , ls , rs); //
}
void DEBUG(int p , int l , int r){
if(l == r){
printf("%d " , qwq[p].maxn);
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
DEBUG(p << 1 , l , mid);
DEBUG(p << 1 | 1 , mid + 1 , r);
}
inline int read() {
int x = 0;
bool f = false;
char ch = getchar();
while(ch > '9' || ch < '0'){
if(ch == '-')
f = true;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return f? -x : x;
}
int main(void){
id = read(); T = read();
while(T--){
ll kkk = 0;
n = read(); m = read();
for(int i = 1;i <= n;i++) a[i] = read();
init(1 , 1 , n);
while(m--){
int op , l , r , x , y;
op = read(); l = read(); r = read();
if(id & 1){
l = ((1LL * l * l) ^ kkk) % n * 2333 % n + 1;
r = ((1LL * r * r) ^ kkk) % n * 2024 % n + 1;
if(l > r) swap(l , r);
}
if(op == 1){
x = read();
minupd(1 , 1 , n , l , r , x);
}
if(op == 2){
x = read();
maxupd(1 , 1 , n , l , r , x);
}
if(op == 3){
Tree ret = solve3(1 , 1 , n , l , r);
ll ans = 1LL * (r - l + 1) * (r - l + 2);
ans -= ret.twt + (r - l + 1 - ret.maxsum);
ans >>= 1;
printf("%lld\n" , ans);
kkk = ans;
}
}
}
}

历史最值问题#

前置:复杂标记信息合并。

一般这种可以用(max,+)(\max,+)矩阵来设计标记,如果再加上区间最值操作呢?直接看模板把。

线段树3#

区间加,区间取min,求区间和,区间最大值,历史区间最大值

还是按照上面思路分两类:最大,非最大。上面说了可以转换成加法,于是考虑如何维护。

这里还是使用矩阵(由于不会直接推)。

显然信息就是区间最大值 a,历史最大 b(这里历史最值和区间和无关,分别维护)。这样。

[ab][xx0]=[a+xmax{b,a+x}]\begin{bmatrix} a\\b \end{bmatrix}\begin{bmatrix} x & x\\-\infty& 0 \end{bmatrix}=\begin{bmatrix} a+x\\\max\{b,a+x\} \end{bmatrix}

让我们暴力拆拆拆。

暴力程序跑出来,显然只需要维护矩阵的第一行两项即可,然后再跑矩阵转移即可。

然后就写完了。

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int inf = 2e9;
struct Tag {
int k1 , k2; //最大值
int k3 , k4; //非最大
inline Tag(){ k1 = k2 = k3 = k4 = 0; }
inline Tag(ll _ , ll __ , ll ___ , ll ____): k1(_) , k2(__) , k3(___) , k4(____) {}
inline void operator += (Tag x){
(*this) = Tag(
k1 + x.k1 , max(k1 + x.k2 , k2) ,
k3 + x.k3 , max(k3 + x.k4 , k4)
);
}
}lz[2000005];
struct Seg {
int a , b , mx2 , cntmx , le; ll s;
inline Seg(){ a = b = mx2 = -inf; cntmx = le = 0; }
inline Seg(ll _ , ll __ , ll ___): a(_) , b(__) , s(___) {}
inline friend Seg operator + (Seg ls , Seg rs){
Seg ret;
ret.b = max(ls.b , rs.b);
ret.s = ls.s + rs.s;
ret.le = ls.le + rs.le;
if(ls.a < rs.a){
ret.a = rs.a; ret.cntmx = rs.cntmx;
ret.mx2 = max(ls.a , rs.mx2);
}
else if(ls.a > rs.a){
ret.a = ls.a; ret.cntmx = ls.cntmx;
ret.mx2 = max(ls.mx2 , rs.a);
}
else{
ret.a = ls.a; ret.cntmx = ls.cntmx + rs.cntmx;
ret.mx2 = max(ls.mx2 , rs.mx2);
}
return ret;
}
inline void operator += (Tag x){
b = max(b , a + x.k2); a += x.k1;
s += 1LL * x.k1 * cntmx + 1LL * x.k3 * (le - cntmx);
if(mx2 != -inf){
b = max(b , mx2 + x.k4);
mx2 += x.k3;
}
}
}tr[2000005];
inline void upd(int p , Tag x){ tr[p] += x; lz[p] += x; }
inline void pushdown(int p){
if(lz[p].k1 == 0 && lz[p].k2 == 0 && lz[p].k3 == 0 && lz[p].k4 == 0) return ;
int mx = max(tr[p << 1].a , tr[p << 1 | 1].a);
if(tr[p << 1].a == mx)
upd(p << 1 , lz[p]);
else
upd(p << 1 , Tag(lz[p].k3 , lz[p].k4 , lz[p].k3 , lz[p].k4));
if(tr[p << 1 | 1].a == mx)
upd(p << 1 | 1 , lz[p]);
else
upd(p << 1 | 1 , Tag(lz[p].k3 , lz[p].k4 , lz[p].k3 , lz[p].k4));
lz[p] = Tag(0 , 0 , 0 , 0);
}
void add(int p , int l , int r , int x , int y , int k){
if(l > y || r < x) return ;
if(l >= x && r <= y){
upd(p , Tag(k , k , k , k));
// printf("q%d %d %d\n" , l , r , tr[p].a);
return ;
}
int mid = (l + r) >> 1;
pushdown(p);
add(p << 1 , l , mid , x , y , k);
add(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
void upd_min(int p , int l , int r , int x , int y , int k){
if(l > y || r < x || tr[p].a <= k) return ;
if(l >= x && r <= y && k > tr[p].mx2){
upd(p , Tag(k - tr[p].a , k - tr[p].a , 0 , 0));
return ;
}
int mid = (l + r) >> 1;
pushdown(p);
upd_min(p << 1 , l , mid , x , y , k);
upd_min(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) return tr[p];
int mid = (l + r) >> 1;
pushdown(p);
if(y <= mid) return query(p << 1 , l , mid , x , y);
if(x > mid ) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}
int a[500005];
void init(int p , int l , int r){
if(l == r){
tr[p].a = tr[p].b = tr[p].s = a[l];
tr[p].cntmx = tr[p].le = 1;
tr[p].mx2 = -inf;
return ;
}
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
int n , m;
int main(void){
scanf("%d%d" , &n , &m);
for(int i = 1;i <= n;i++) scanf("%d" , &a[i]);
init(1 , 1 , n);
while(m--){
int op , l , r; scanf("%d%d%d" , &op , &l , &r);
if(op == 1){
int x; scanf("%d" , &x);
add(1 , 1 , n , l , r , x);
}
if(op == 2){
int x; scanf("%d" , &x);
upd_min(1 , 1 , n , l , r , x);
}
if(op == 3) printf("%lld\n" , query(1 , 1 , n , l , r).s);
if(op == 4) printf("%d\n" , query(1 , 1 , n , l , r).a);
if(op == 5) printf("%d\n" , query(1 , 1 , n , l , r).b);
}
}

CPU监控#

这题就可以用(max,+)(\max,+)矩阵。

很容易想到维护当前最大值aa,和历史最大值bb

[ab][kk0]=[a+kmax{a+k,b}]\begin{bmatrix}a\\b\\ \end{bmatrix}\begin{bmatrix}k&k\\-\infty&0\\ \end{bmatrix}=\begin{bmatrix}a+k\\\max\{a+k,b\}\\ \end{bmatrix}

如果再加上推平,就需要再多加一个00

[ab0][0kk0]=[kmax{b,k}0]\begin{bmatrix}a\\b\\0\\ \end{bmatrix}\begin{bmatrix}-\infty&-\infty&-\infty\\-\infty&0&-\infty\\k&k&0\\ \end{bmatrix}=\begin{bmatrix}k\\\max\{b,k\}\\0\\ \end{bmatrix}

不难发现只要第一行第一二个和第三行第一二个会变,于是维护这些,然后跑上面的暴力转移代码即可。

#include <cstdio>
#include <algorithm>
using namespace std;
const int inf = 2147480000;
int n , m , a[100005];
inline int Add(int x , int y){
if(x <= -inf || y <= -inf) return -inf;
return x + y;
}
struct Tag {
int k11 , k12 , k21 , k22;
inline Tag(){ k11 = 0; k12 = k21 = k22 = -inf; }
inline Tag(int _ , int __): k11(_) , k12(_) , k21(__) , k22(__) {}
inline friend Tag operator + (Tag l , Tag r){
// if(l.k11 == 0 && l.k12 == -inf && l.k21 == -inf && l.k22 == -inf) return r;
Tag ret;
ret.k11 = Add(l.k11 , r.k11);
ret.k12 = max(l.k12 , Add(l.k11 , r.k12));
ret.k21 = max(Add(l.k21 , r.k11) , r.k21);
ret.k22 = max(Add(l.k21 , r.k12) , max(l.k22 , r.k22));
return ret;
}
inline void operator += (Tag x){ (*this) = (*this) + x; }
}lazy[400005];
struct Seg {
int maxn , ans;
inline Seg(){ maxn = ans = -inf; }
inline Seg(int _ , int __): maxn(_) , ans(__) {}
inline friend Seg operator + (Seg ls , Seg rs){
return Seg(
max(ls.maxn , rs.maxn) ,
max(ls.ans , rs.ans)
);
}
inline void operator += (Tag rs){
ans = max(ans , max(Add(maxn , rs.k12) , rs.k22));
maxn = max(Add(maxn , rs.k11) , rs.k21);
ans = max(ans , maxn);
}
}tr[400005];
inline void pushdown(int p){
if(lazy[p].k11 != 0 || lazy[p].k12 != -inf || lazy[p].k21 > -inf || lazy[p].k22 > -inf){
tr[p << 1] += lazy[p];
tr[p << 1 | 1] += lazy[p];
lazy[p << 1] += lazy[p];
lazy[p << 1 | 1] += lazy[p];
lazy[p] = Tag();
}
}
void init(int p , int l , int r){
if(l == r){
tr[p].maxn = tr[p].ans = a[l];
return ;
}
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
void upd(int p , int l , int r , int x , int y , Tag k){
if(l > y || r < x) return ;
if(l >= x && r <= y){ tr[p] += k; lazy[p] += k; return ; }
int mid = (l + r) >> 1;
pushdown(p);
upd(p << 1 , l , mid , x , y , k);
upd(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) { return tr[p];
}
int mid = (l + r) >> 1;
pushdown(p);
if(y <= mid) return query(p << 1 , l , mid , x , y);
if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}
int main(void){
scanf("%d" , &n);
for(int i = 1;i <= n;i++) scanf("%d" , &a[i]);
init(1 , 1 , n);
scanf("%d" , &m);
while(m--){
char s[5]; scanf("%s" , s + 1);
if(s[1] == 'Q'){
int l , r; scanf("%d%d" , &l , &r);
printf("%d\n" , query(1 , 1 , n , l , r).maxn);
}
if(s[1] == 'A'){
int l , r; scanf("%d%d" , &l , &r);
printf("%d\n" , query(1 , 1 , n , l , r).ans);
}
if(s[1] == 'P'){
int l , r , x; scanf("%d%d%d" , &l , &r , &x);
upd(1 , 1 , n , l , r , Tag(x , -inf));
}
if(s[1] == 'C'){
int l , r , x; scanf("%d%d%d" , &l , &r , &x);
upd(1 , 1 , n , l , r , Tag(-inf , x));
}
}
}
/*
5
-5 -3 3 -5 -3
3
P 1 3 3
C 1 3 5
A 2 4
*/

SP1557 GSS2#

区间最大子段和,相同数贡献仅算一次。

可以考虑离线,按照 r 排序,然后依次加入。

每个点维护当前加入 i 的和,于是比较显然,加一个点就相当于(pre,i](pre,i]的区间加。

显然答案需要历史最大值,于是只需要实现区间加,区间查历史最大值就做完了。

由于 SPOJ 我交不了,就没写代码。

zkw 线段树#

这里仅提供模板。

int N; //区间加,区间和。常数小。
ll qwq[400005] , lazy[400005];
inline void init(){
N = 1;
while(N < n + 2) N <<= 2;
for(int i = 1;i <= n;i++) qwq[i + N] = a[i];
for(int i = N - 1;i > 0;i--){
qwq[i] = qwq[i << 1] + qwq[i << 1 | 1];
lazy[i] = 0;
}
}
inline ll query(int l , int r){
int L , R , len , ln = 0 , rn = 0;
ll ret = 0;
for(L = N + l - 1 , R = N + r + 1 , len = 1;L ^ R ^ 1;L >>= 1 , R >>= 1 , len <<= 1){
if(lazy[L]) ret += lazy[L] * ln;
if(lazy[R]) ret += lazy[R] * rn;
if(~L & 1) ret += qwq[L ^ 1] , ln += len;
if( R & 1) ret += qwq[R ^ 1] , rn += len;
}
for(;L;L >>= 1 , R >>= 1){
ret += lazy[L] * ln;
ret += lazy[R] * rn;
}
return ret;
}
inline void add(int l , int r , ll x){
int L , R , len , ln = 0 , rn = 0;
for(L = N + l - 1 , R = N + r + 1 , len = 1;L ^ R ^ 1;L >>= 1 , R >>= 1 , len <<= 1){
qwq[L] += x * ln; qwq[R] += x * rn;
if(~L & 1) lazy[L ^ 1] += x , qwq[L ^ 1] += x * len , ln += len;
if( R & 1) lazy[R ^ 1] += x , qwq[R ^ 1] += x * len , rn += len;
}
for(;L;L >>= 1 , R >>= 1){
qwq[L] += x * ln;
qwq[R] += x * rn;
}
}

选讲#

HDU6315(势能线段树)#

ai=0a_i=0bbnn的排列。

  • aa区间加。
  • 求区间ai/bi\sum \lfloor a_i/b_i \rfloor

n,m105n,m\le 10^5

注意到答案最大为 n/1+...n/nn/1+...n/n,大约是1e6,于是维护每个数还要加几次才会更新答案,如果区间最小为 11,则lognlog n找到这个点,改答案,总时间复杂度为O(nlog2n)O(n\log^2 n)

Ynoi P5354 由乃的OJ#

比较显然,先树剖转链。

然后可以建6464棵线段树,然后每颗树维护这段区间传入0/10/1出来的值,然后贪心地选11即可。O(mklogn)O(mk\log n)很可惜过不了。

其实因为运算符一样,可以一起计算:线段树维护传如000.../1111...000.../1111...出来的值,然后考虑如何合并左右子树。

这样即可:

tr[p][0] = ((tr[p << 1 | 1][0] & (~tr[p << 1][0])) | (tr[p << 1 | 1][1] & tr[p << 1][0]));
tr[p][1] = ((tr[p << 1 | 1][0] & (~tr[p << 1][1])) | (tr[p << 1 | 1][1] & tr[p << 1][1]));

。。。

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef unsigned long long ll;
int n , m , k; pair <int , ll> a[100005];
vector <int> g[100005];
int dep[100005] , son[100005] , fa[100005] , siz[100005];
void dfs1(int x , int f , int d){
siz[x] = 1; fa[x] = f; dep[x] = d;
int maxn = -1;
for(int nxt : g[x]){
if(nxt == f) continue;
dfs1(nxt , x , d + 1);
siz[x] += siz[nxt];
if(siz[nxt] > maxn){
maxn = siz[nxt];
son[x] = nxt;
}
}
}
int tp[100005] , id[100005] , cnt; pair <int , ll> w[100005];
void dfs2(int x , int top){
tp[x] = top;
w[id[x] = ++cnt] = a[x];
if(son[x]) dfs2(son[x] , top);
for(int nxt : g[x]){
if(nxt == fa[x] || nxt == son[x]) continue;
dfs2(nxt , nxt);
}
}
ll tr[400005][4];
inline void pushup(int p){
tr[p][0] = ((tr[p << 1 | 1][0] & (~tr[p << 1][0])) | (tr[p << 1 | 1][1] & tr[p << 1][0]));
tr[p][1] = ((tr[p << 1 | 1][0] & (~tr[p << 1][1])) | (tr[p << 1 | 1][1] & tr[p << 1][1]));
tr[p][2] = ((tr[p << 1][2] & (~tr[p << 1 | 1][2])) | (tr[p << 1][3] & tr[p << 1 | 1][2]));
tr[p][3] = ((tr[p << 1][2] & (~tr[p << 1 | 1][3])) | (tr[p << 1][3] & tr[p << 1 | 1][3]));
}
inline pair <ll , ll> merge(pair <ll , ll> ls , pair <ll , ll> rs , bool t = 0){
pair <ll , ll> ret;
if(t) swap(ls , rs);
ret.first = ((rs.first & (~ls.first )) | (rs.second & ls.first ));
ret.second = ((rs.first & (~ls.second)) | (rs.second & ls.second));
return ret;
}
void init(int p , int l , int r){
if(l == r){
tr[p][0] = 0; tr[p][1] = ~(ll)0;
if(w[l].first == 1) tr[p][0] &= w[l].second , tr[p][1] &= w[l].second;
if(w[l].first == 2) tr[p][0] |= w[l].second , tr[p][1] |= w[l].second;
if(w[l].first == 3) tr[p][0] ^= w[l].second , tr[p][1] ^= w[l].second;
tr[p][2] = tr[p][0]; tr[p][3] = tr[p][1];
return ;
}
int mid = (l + r) >> 1;
init(p << 1 , l , mid);
init(p << 1 | 1 , mid + 1 , r);
pushup(p);
}
void upd(int p , int l , int r , int x , pair <int , ll> y){
if(l > x || r < x) return ;
if(l == r){
tr[p][0] = 0; tr[p][1] = ~(ll)0;
if(y.first == 1) tr[p][0] &= y.second , tr[p][1] &= y.second;
if(y.first == 2) tr[p][0] |= y.second , tr[p][1] |= y.second;
if(y.first == 3) tr[p][0] ^= y.second , tr[p][1] ^= y.second;
tr[p][2] = tr[p][0]; tr[p][3] = tr[p][1];
return ;
}
int mid = (l + r) >> 1;
upd(p << 1 , l , mid , x , y);
upd(p << 1 | 1 , mid + 1 , r , x , y);
pushup(p);
}
pair <ll , ll> query(int p , int l , int r , int x , int y, int k = 0){
if(l >= x && r <= y) return {tr[p][k] , tr[p][1 + k]};
int mid = (l + r) >> 1;
if(y <= mid) return query(p << 1 , l , mid , x , y , k);
if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y , k);
return merge(
query(p << 1 , l , mid , x , y , k) ,
query(p << 1 | 1 , mid + 1 , r , x , y , k) ,
(k > 0)
);
}
inline pair <ll , ll> getans(int x , int y){
pair <ll , ll> ret1 , ret2 , ret3;
bool f1 = 1 , f2 = 1;
while(tp[x] != tp[y]){
if(dep[tp[x]] >= dep[tp[y]]){
if(f1) ret1 = query(1 , 1 , n , id[tp[x]] , id[x] , 2) , f1 = 0;
else ret1 = merge(ret1 , query(1 , 1 , n , id[tp[x]] , id[x] , 2));
x = fa[tp[x]];
}
else{
if(f2) ret2 = query(1 , 1 , n , id[tp[y]] , id[y]) , f2 = 0;
else ret2 = merge(query(1 , 1 , n , id[tp[y]] , id[y]) , ret2);
y = fa[tp[y]];
}
}
if(dep[x] < dep[y]) ret3 = query(1 , 1 , n , id[x] , id[y]);
else ret3 = query(1 , 1 , n , id[y] , id[x] , 2);
if(f1 && f2) return ret3;
if(f1) return merge(ret3 , ret2);
if(f2) return merge(ret1 , ret3);
return merge(merge(ret1 , ret3) , ret2);
}
int main(void){
scanf("%d%d%d" , &n , &m , &k);
for(int i = 1;i <= n;i++) scanf("%d%llu" , &a[i].first , &a[i].second);
for(int i = 1 , u , v;i < n;i++){
scanf("%d%d" , &u , &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1 , 0 , 1); dfs2(1 , 1);
init(1 , 1 , n);
while(m--){
int op , x , y; ll z;
scanf("%d%d%d%llu" , &op , &x , &y , &z);
if(op == 1){
pair <ll , ll> ret = getans(x , y);
ll ans = 0 , nw = 0;
for(int i = k - 1;i >= 0;i--){
bool a0 = (ret.first >> i & 1ull);
bool a1 = (ret.second >> i & 1ull);
if(a0) ans |= ((ll)1 << i);
else if(a1 && (nw | ((ll)1 << i)) <= z){
ans |= ((ll)1 << i);
nw |= ((ll)1 << i);
}
}
printf("%llu\n" , ans);
}
if(op == 2)
upd(1 , 1 , n , id[x] , {y , z});
}
}

P11334#

显然可以线段树,因为是可合并信息。

考虑每段区间维护最长等差数列长度,于是为了合并它,就需要再维护区间前缀最长等差数列长度,和后缀最长。而且合并时也需要判断是否能合并,所以还需要维护前缀等差数列和后缀等差数列的首尾项。(可以类比线段树维护最大子段和)

综上,需要维护五个值,区间内的答案,区间前缀最大,后缀最大,前缀等差数列,后缀等差数列。

代码特别难写。。考虑情况特别多。

  • long long 没开全,1打成2,推平应先下放,merge情况考虑少了,洛谷评测太慢了。

P9631#

一操作简单区间取最值不说,重点在二操作,有个结论,我不会证,,,就是这种游戏下,如果异或和为 0 则先手必输。

于是可以先维护区间异或和,然后尽量让第二步的人的异或和为 0,这样就是必胜的了。

因为是拿,于是要满足aixaia_i\oplus x \le a_i的才可以拿,算这玩意有几个即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int lz[800005];
struct Seg {
int mi , cntmi , mi2 , ans;
int tot[31];
inline Seg(){ memset(tot , 0 , sizeof(tot)); }
inline friend Seg operator + (Seg ls , Seg rs){
Seg ret;
ret.ans = (ls.ans ^ rs.ans);
if(ls.mi < rs.mi){
ret.mi = ls.mi; ret.cntmi = ls.cntmi;
ret.mi2 = min(ls.mi2 , rs.mi);
}
else if(ls.mi > rs.mi){
ret.mi = rs.mi; ret.cntmi = rs.cntmi;
ret.mi2 = min(ls.mi , rs.mi2);
}
else{
ret.mi = ls.mi; ret.cntmi = ls.cntmi + rs.cntmi;
ret.mi2 = min(ls.mi2 , rs.mi2);
}
for(int i = 0;i <= 30;i++) ret.tot[i] = ls.tot[i] + rs.tot[i];
return ret;
}
inline void operator += (int x){
if(cntmi & 1) ans ^= mi ^ x;
for(int i = 0;i <= 30;i++) if(mi >> i & 1)
tot[i] -= cntmi;
mi = x;
for(int i = 0;i <= 30;i++) if(mi >> i & 1)
tot[i] += cntmi;
}
}tr[800005];
inline void upd(int p , int x){ tr[p] += x; lz[p] = x; }
inline void pushdown(int p){
if(!lz[p]) return ;
int mi = min(tr[p << 1].mi , tr[p << 1 | 1].mi);
if(tr[p << 1].mi == mi) upd(p << 1 , lz[p]);
if(tr[p << 1 | 1].mi == mi) upd(p << 1 | 1 , lz[p]);
lz[p] = 0;
}
void upd_max(int p , int l , int r , int x , int y , int k){
if(l > y || r < x || tr[p].mi >= k) return ;
if(l >= x && r <= y && tr[p].mi2 > k){
upd(p , k);
return ;
}
int mid = (l + r) >> 1;
pushdown(p);
upd_max(p << 1 , l , mid , x , y , k);
upd_max(p << 1 | 1 , mid + 1 , r , x , y , k);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
Seg query(int p , int l , int r , int x , int y){
if(l >= x && r <= y) return tr[p];
int mid = (l + r) >> 1;
pushdown(p);
if(y <= mid) return query(p << 1 , l , mid , x , y);
if(x > mid) return query(p << 1 | 1 , mid + 1 , r , x , y);
return (
query(p << 1 , l , mid , x , y) +
query(p << 1 | 1 , mid + 1 , r , x , y)
);
}
int a[200005];
void init(int p , int l , int r){
if(l == r){
tr[p].mi = tr[p].ans = a[l];
tr[p].cntmi = 1; tr[p].mi2 = (1 << 30);
for(int i = 0;i <= 30;i++) if(a[l] >> i & 1)
tr[p].tot[i]++;
return ;
}
int mid = (l + r) >> 1;
init(p << 1 , l , mid); init(p << 1 | 1 , mid + 1 , r);
tr[p] = tr[p << 1] + tr[p << 1 | 1];
}
int n , q;
int main(void){
scanf("%d%d" , &n , &q);
for(int i = 1;i <= n;i++) scanf("%d" , &a[i]);
init(1 , 1 , n);
while(q--){
int op , l , r , x;
scanf("%d%d%d%d" , &op , &l , &r , &x);
if(op == 1) upd_max(1 , 1 , n , l , r , x);
if(op == 2){
Seg ans = query(1 , 1 , n , l , r);
ans.ans ^= x;
// printf("w%d\n" , ans.ans);
int t = -1;
for(int i = 30;i >= 0;i--) if(ans.ans >> i & 1){ t = i; break; }
if(t == -1) puts("0");
else printf("%d\n" , ans.tot[t] + (x >> t & 1));
}
}
}

P5524 Ynoi 2012 NOIP2015充满了希望#

  • 交换
  • 推平
  • 查询

给你操作,问顺序执行[l,r][l,r]的操作后所有查询操作的和。

n,m,q106n,m,q\le 10^6

我们用线段树记录每个点最后一次执行的二操作的序号。

对于一操作,也是交换。

对于二操作,还是区间推平。

对于三操作,贡献即为vtiv_{t_i}

那么将询问离线。枚举rr,维护sis_i表示[i,r][i,r]之间的答案,移动方法如上,需要单点查询,区间加,可以用树状数组。于是做完了。

代码还没写。

扫描线#

参考:https://www.luogu.com.cn/article/9vwv8pe7

答案贡献类#

可以考虑每个点要区间满足什么要求使得这个点可以对答案做贡献


常用技巧:

lxl《树套堆》

区间取min,撤销第 i 次操作,求单点值。

如题,树套堆。线段树每个节点维护一个堆,然后标记永久化。

O(log2n)O(\log^2 n)

#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int inf = 1e9;
struct Zbojin_Queue {
priority_queue <int , vector <int> , greater <int> > q , death;
inline void push(int x){ q.push(x); }
inline void erase(int x){ death.push(x); }
inline int top(){
while(!death.empty() && q.top() == death.top())
q.pop() , death.pop();
return q.empty()? inf : q.top();
}
}qwq[2000005];
void upd(int p , int l , int r , int x , int y , int k){
if(r < x || l > y) return ;
if(l >= x && r <= y){
if(k > 0) qwq[p].push(k);
else qwq[p].erase(-k);
return ;
}
int mid = (l + r) >> 1;
upd(p << 1 , l , mid , x , y , k);
upd(p << 1 | 1 , mid + 1 , r , x , y , k);
}
int getmin(int p , int l , int r , int x){
if(l > x || r < x) return inf;
int ret = qwq[p].top();
if(l == r) return ret;
int mid = (l + r) >> 1;
return min(ret , min(
getmin(p << 1 , l , mid , x) ,
getmin(p << 1 | 1 , mid + 1 , r , x)
));
}
int main(void){
}

这个方法也可以用在:

区间推平,区间撤销推平

只需要把值变成 pair,第一个值是操作时间,第二个是推平的值。

然后因为是pair,所以堆(大根堆)顶的值一定是时间最大的,也就是最近推平的。删除同理。

其实因为插入是按时间顺序的,因此可以用栈来代替。时间复杂度少个log。

如果用栈,建议用手写的,由于不知名原因,用STL的stack空间太大了。。。。

例题#

CF522D Closest Equals#

注意到,显然只有最近的两个相同的点才能对答案做贡献,并且答案区间要包含这两个点,即 li,jrl\le i, j \le r,用上面的树套堆维护扫描线即可。

code

CF1000F One Occurrence#

prei,nxtipre_i,nxt_i,为ii前一个相同和后一个相同的。假如要点xx有贡献,那么区间必须满足l>prex,r<nxtxl>pre_x,r<nxt_x,扫描线维护即可。

code

CF453E Little Pony and Lord Tirek(-10)#

假如aia_i均为00,可以算出这个点最小需要几时才能到最大值,记为fif_i,那么容易想到用珂朵莉树维护修改的时间,于是就有多个连续块。

tt为每个块的时间差,然后每个点分两种情况:

  • t>fit>f_i:这个点为mim_i
  • tfit \le f_i:这个点为 tritr_i

可以预先把每个点放到两个二维平面上,xx轴代表iiyy轴代表ff,坐标则为(i,fi)(i,f_i),权值为mim_irir_i

那么每次询问相当于求第一个二维平面[l,t+1,r,MAX][l,t+1,r,MAX]矩形的和,加上第二个二维平面[l,1,r,t][l,1,r,t]矩形的和。于是离线下来扫描线,扫描线可以用树状数组比较快。

然后是ai0a_i\not = 0时,只需要在第一次修改时暴力处理,由于修改一次后都为00,所以时间复杂度不会增加。

  • -1:没写完,交一发看看。
  • -2:调RE的问题,树状数组00的会RE。。
  • -3~-6:加ai0a_i\not =0的情况,调。
  • -7&-8:数组开小。
  • -9&-10:long long&快读。

code

题解

时间扫描类#

常用技巧:时光倒流。

例题#

P3863 序列#

区间加,求 i 点之前有几个时刻的值大于 y

实现以时间和下标建系,修改是区间加,查询是区间排名,分块。

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
int n , m; ll a[100005];
char buf[1 << 21] , *p1 , *p2;
#define GC p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin) , p1 == p2) ? EOF : *p1++
inline int read() {
int x = 0;
bool f = false;
char ch = GC;
while(ch > '9' || ch < '0'){
if(ch == '-')
f = true;
ch = GC;
}
while(ch >= '0' && ch <= '9'){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = GC;
}
return f? -x : x;
}
namespace LOVE {
const int kuai = 220;
int L[(100005 / kuai) + 5] , R[(100005 / kuai) + 5] , id[100005];
ll b[100005] , lz[(100005 / kuai) + 5];
inline void add(int l , int r , int x){
const int st = id[l] , ed = id[r];
if(st == ed){
for(int i = l;i <= r;i++) a[i] += x;
for(int i = L[st];i <= R[st];i++) b[i] = a[i];
sort(b + L[st] , b + R[st] + 1);
return ;
}
for(int i = l;i <= R[st];i++) a[i] += x;
for(int i = L[st];i <= R[st];i++) b[i] = a[i];
sort(b + L[st] , b + R[st] + 1);
for(int i = st + 1;i < ed;i++) lz[i] += x;
for(int i = L[ed];i <= r;i++) a[i] += x;
for(int i = L[ed];i <= R[ed];i++) b[i] = a[i];
sort(b + L[ed] , b + R[ed] + 1);
}
inline int query(int l , int r , ll x){
const int st = id[l] , ed = id[r];
int ret = 0;
if(st == ed){
for(int i = l;i <= r;i++) ret += (a[i] + lz[st] >= x);
return ret;
}
for(int i = l;i <= R[st];i++) ret += (a[i] + lz[st] >= x);
for(int i = st + 1;i < ed;i++)
ret += (R[i] + b + 1 - lower_bound(b + L[i] , b + R[i] + 1 , x - lz[i]));
for(int i = L[ed];i <= r;i++) ret += (a[i] + lz[ed] >= x);
return ret;
}
inline void init(){
const int N = m / kuai + 1;
for(int i = 1;i <= N;i++){
L[i] = R[i - 1] + 1;
R[i] = min(R[i - 1] + kuai , m + 1);
for(int j = L[i];j <= R[i];j++) id[j] = i;
}
}
}
struct QWQ { int l , r , k , id; };
vector <QWQ> u[100005] , q[100005];
int ans[100005];
int main(void){
n = read(); m = read();
for(int i = 1 , x , lt = 0;i <= n;i++){
x = read();
u[i].push_back((QWQ){0 , m , x - lt , 0});
lt = x;
}
LOVE::init();
int nw = 0;
for(int i = 1;i <= m;i++){
int op , l , r; op = read(); l = read(); r = read();
if(op == 1){
int x; x = read();
u[l].push_back((QWQ){i , m , x , 0});
u[r + 1].push_back((QWQ){i , m , -x , 0});
}
if(op == 2){
q[l].push_back((QWQ){0 , i - 1 , r , ++nw});
}
}
for(int i = 0;i <= n;i++){
for(QWQ j : u[i]) LOVE::add(j.l + 1 , j.r + 1 , j.k);
for(QWQ j : q[i]) ans[j.id] = LOVE::query(j.l + 1 , j.r + 1 , j.k);
}
for(int i = 1;i <= nw;i++) printf("%d\n" , ans[i]);
}

致谢&参考文献#

浅谈扫描线 Larry76

论文《区间最值操作与历史最值问题》——吉如一

区间最值操作与区间历史最值详解 灵梦

https://www.luogu.me/article/rqmfqvmu

https://www.cnblogs.com/Meatherm/p/17925813.html#:~

lxl 的课件。

oi-wki 等

感谢 fen sir 对我线段树标题的评论

感谢冯某对我NOIP 2022 比赛解答。

感谢线段树对本文的大力支持。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

部分信息可能已经过时