Ⅰ、拋出問題
Description
有一列元素,每一個元素有三個屬性:標號、標識符、數值。這些元素按照標號從1~n排列,標識符也是1~n的一個排列,初始時數值為0。當然我們可以把每個元素看成一個多維數字,那么這列元素就是一個數列。
現在請你維護這個數列,使其能支持以下兩種操作:1.將標號為l~r的所有元素的數值先乘上x,再加上y;2.將標識符為l~r的所有元素的數值先乘上x,再加上y。當然你還得回答某些詢問:1.標號為l~r的所有元素的數值的和;2.標識符為l~r的所有元素的數值的和。
Input
第一行有兩個正整數n、m,分別表示數列長度和操作與詢問個數的總和。第二行有n個正整數,表示每個元素的標識符,保證這n個數是1~n的一個排列。接下來m行,每行的第一個數字為op。若op為0,則表示要進行第一個操作,接下去四個數字表示l,r,x,y;若op為1,則表示要進行第二個操作,接下去四個數字表示l,r,x,y;若op為2,則表示要回答第一個詢問,接下去兩個數字表示l,r;若op為3,則表示要回答第二個詢問,接下去兩個數字表示l,r。
Output
包含若干行,每行表示一個詢問的答案。由於答案可能很大,只要請你輸出答案對536870912取模后的值即可。
Sample Input
4 4
2 1 4 3
0 2 3 4 5
1 1 3 4 7
2 1 1
3 1 1
Sample Output
7
27
HINT
第一次操作后,數列變為0 5 5 0
第二次操作后,數列變為7 27 5 7
N,M<=50000, 1<=L<=R<=N 0<=X,Y<=2^31-1
Source
bzoj4303數列
Ⅱ、分析問題
kdtree,全稱k-dimensional-tree,意思即為k維樹,主要用於解決高維空間的修改查詢操作,支持打標記,求最近最遠點對等,類似於線段樹等數據結構,接下來就來詳細講講kdtree的寫法
1、維護的數據
寫數據結構,一定要弄清維護了哪些數據
kdtree是一種類似於線段樹一樣的數據結構,樹上每一個節點管轄k維區間中的某一個范圍,存每個維度的最大最小值以確定邊界
代碼:
注:代碼中給的是二維kdtree的模板,所以只有兩位
struct hahaha{
int tp,ls,rs,v[2],Max[2],Min[2],val;//tp為當前節點維護的是哪一維,ls,rs分別為左右兒子編號,v存節點坐標,Max和Min維護當前節點管轄區間的最大最小(即邊界),val存當前點的權值
int cnt,mlt,sum,len;//cnt加標記,mlt乘標記,sum區間和,len區間長度
bool operator<(const hahaha &y)const{
return v[T]<y.v[T];//排序方便尋找中位數
}
}tree[50010];
2、建樹
如圖,可以看出,kdtree是以一位一位順次分割的方式建樹的,
每次尋找區間中的中位數點,沿當前維度進行分割,如本圖為先豎着再橫着分割
第一次先找到橫向的中位數,豎着分割一次(已用紅色標出),在遞歸左右子樹,找豎着的中位數,橫向分割,再往下依次遞歸
以下建樹部分代碼:
inline void updata(int p){//很顯然的更新
tree[p].Min[0]=min(tree[p].v[0],min(tree[ls(p)].Min[0],tree[rs(p)].Min[0]));//x坐標的最小值
tree[p].Min[1]=min(tree[p].v[1],min(tree[ls(p)].Min[1],tree[rs(p)].Min[1]));//y坐標的最小值
tree[p].Max[0]=max(tree[p].v[0],max(tree[ls(p)].Max[0],tree[rs(p)].Max[0]));//x坐標的最大值
tree[p].Max[1]=max(tree[p].v[1],max(tree[ls(p)].Max[1],tree[rs(p)].Max[1]));//y坐標的最大值
}
inline int build_tree(int l,int r,int tp){//l,r為區間,tp為區間維度
T=tp;//T也是區間維度,用於查找中位數
int mid=((l+r)>>1),p=mid;
nth_element(tree+l,tree+mid,tree+r+1);//這是個查找中位數的神奇函數
tree[p].tp=tp;
tree[p].mlt=1;
tree[p].len=r-l+1;//更新節點信息
if(l<mid)
ls(p)=build_tree(l,mid-1,tp^1);//其實這個地方嚴謹來說應該是(tp+1)%2,因為維度是順次遍歷,假如說有5維,那就是按照0,1,2,3,4,0,1,2...這樣的順序遍歷。
//注意,這里不是像線段樹一樣l,mid,而是l,mid-1,因為左區間不包括這個點本身
//之所以這樣寫是為了卡常
if(r>mid)
rs(p)=build_tree(mid+1,r,tp^1);
updata(p);//再次更新節點信息
return p;
}
3、修改操作
修改操作指的是將在某個范圍內的所有節點的權值更改,支持像線段樹一樣打標記和下傳標記
具體見代碼
//注:本題要求的是先乘上一個數再加上一個數,所以有兩個標記數組
inline void Add_mlt(int p,int v){
tree[p].val*=v;
tree[p].cnt*=v;
tree[p].mlt*=v;
tree[p].sum*=v;
}
inline void Add_cnt(int p,int v){
tree[p].val+=v;
tree[p].cnt+=v;
tree[p].sum+=tree[p].len*v;
}
inline void pushdown(int p){
if(tree[p].mlt!=1){//下傳乘標記
Add_mlt(ls(p),tree[p].mlt);
Add_mlt(rs(p),tree[p].mlt);
tree[p].mlt=1;
}
if(tree[p].cnt!=0){//下傳加標記
Add_cnt(ls(p),tree[p].cnt);
Add_cnt(rs(p),tree[p].cnt);
tree[p].cnt=0;
}
}
inline void change(int p,int x,int y,int mt,int ct){//p為當前節點,將第T維(T為全局變量,記錄當前處理維度)坐標在x與y之間的數都乘上mt,加上ct
if(tree[p].Max[T]<x||y<tree[p].Min[T])//如果不在要處理的范圍內,退出
return ;
if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y){//如果都在要處理的范圍內,就打上乘標記與加標記
Add_mlt(p,mt);//加乘標記
Add_cnt(p,ct);//加加標記
return ;
}
pushdown(p);//下傳標記
if(x<=tree[p].v[T]&&tree[p].v[T]<=y)//如果當前點在處理范圍內
tree[p].val=tree[p].val*mt+ct;//處理當前節點
change(ls(p),x,y,mt,ct);//修改左子樹
change(rs(p),x,y,mt,ct);//修改右子樹
tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum+tree[p].val;//更新當前節點
}
5、查詢操作
詳見注釋
inline int ask(int p,int x,int y){//查詢T維x到y的和
if(tree[p].Max[T]<x||y<tree[p].Min[T])//出范圍就return 0
return 0;
if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y)//在范圍之內就返回和
return tree[p].sum;
pushdown(p);//下傳標記
int res=ask(ls(p),x,y)+ask(rs(p),x,y);//加上左右子樹
if(x<=tree[p].v[T]&&tree[p].v[T]<=y)
res+=tree[p].val;//加上這個點本身
return res;
}
至此,kdtree算法的講解就到此結束,讓我們回到原題
容易想到可以吧原題中的i到j看作一維,吧\(p_i\)到\(p_j\)看作第二維,這樣就可以看作是在一個二維平面上進行操作
代碼(模板):
#include<bits/stdc++.h>
#define F(i,j,n) for(register int i=j;i<=n;i++)
#define INF 0x3f3f3f3f
#define ll long long
#define mem(i,j) memset(i,j,sizeof(i))
using namespace std;
#define Md 536870912
int n,m,T;
inline int read(){
int datta=0;char chchc=getchar();bool okoko=0;
while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();}
while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();}
return okoko?-datta:datta;
}
class kd_tree{//之所以用class是為了裝逼
private:
public:
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
int tot,rt;
struct hahaha{
int tp,ls,rs,v[2],Max[2],Min[2],val;
int cnt,mlt,sum,len;
bool operator<(const hahaha &y)const{
return v[T]<y.v[T];
}
}tree[50010];
inline void Add(int *a,int v){
a[0]=a[1]=v;
}
inline void updata(int p){
tree[p].Min[0]=min(tree[p].v[0],min(tree[ls(p)].Min[0],tree[rs(p)].Min[0]));
tree[p].Min[1]=min(tree[p].v[1],min(tree[ls(p)].Min[1],tree[rs(p)].Min[1]));
tree[p].Max[0]=max(tree[p].v[0],max(tree[ls(p)].Max[0],tree[rs(p)].Max[0]));
tree[p].Max[1]=max(tree[p].v[1],max(tree[ls(p)].Max[1],tree[rs(p)].Max[1]));
}
inline int build_tree(int l,int r,int tp){
T=tp;
int mid=((l+r)>>1),p=mid;
nth_element(tree+l,tree+mid,tree+r+1);
tree[p].tp=tp;
tree[p].mlt=1;
tree[p].len=r-l+1;
if(l<mid)
ls(p)=build_tree(l,mid-1,tp^1);
if(r>mid)
rs(p)=build_tree(mid+1,r,tp^1);
updata(p);
return p;
}
inline void init(){
Add(tree[0].Max,-INF);
Add(tree[0].Min,INF);
F(i,1,n)
tree[i].v[0]=i,tree[i].v[1]=read();
rt=build_tree(1,tot=n,0);
}
inline void Add_mlt(int p,int v){
tree[p].val*=v;
tree[p].cnt*=v;
tree[p].mlt*=v;
tree[p].sum*=v;
}
inline void Add_cnt(int p,int v){
tree[p].val+=v;
tree[p].cnt+=v;
tree[p].sum+=tree[p].len*v;
}
inline void pushdown(int p){
if(tree[p].mlt!=1){
Add_mlt(ls(p),tree[p].mlt);
Add_mlt(rs(p),tree[p].mlt);
tree[p].mlt=1;
}
if(tree[p].cnt!=0){
Add_cnt(ls(p),tree[p].cnt);
Add_cnt(rs(p),tree[p].cnt);
tree[p].cnt=0;
}
}
inline void change(int p,int x,int y,int mt,int ct){
if(tree[p].Max[T]<x||y<tree[p].Min[T])
return ;
if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y){
Add_mlt(p,mt);
Add_cnt(p,ct);
return ;
}
pushdown(p);
if(x<=tree[p].v[T]&&tree[p].v[T]<=y)
tree[p].val=tree[p].val*mt+ct;
change(ls(p),x,y,mt,ct);
change(rs(p),x,y,mt,ct);
tree[p].sum=tree[ls(p)].sum+tree[rs(p)].sum+tree[p].val;
}
inline int ask(int p,int x,int y){
if(tree[p].Max[T]<x||y<tree[p].Min[T])
return 0;
if(x<=tree[p].Min[T]&&tree[p].Max[T]<=y)
return tree[p].sum;
pushdown(p);
int res=ask(ls(p),x,y)+ask(rs(p),x,y);
if(x<=tree[p].v[T]&&tree[p].v[T]<=y)
res+=tree[p].val;
return res;
}
}K;
int main(){
n=read();m=read();
K.init();
F(i,1,m){
int opt=read(),l=read(),r=read(),x,y;
if(opt<=1){
x=read();y=read();
T=opt;
K.change(K.rt,l,r,x,y);
}
if(opt>=2){
T=opt-2;
printf("%d\n",K.ask(K.rt,l,r)&(Md-1));//不這么寫會T
}
}
return 0;
}