常見的線段樹是將左右兩個區間的標記合併。然而有些時候傳統的標記無法合併(比如不滿足結合律的運算),這時候就需要用特殊的標記。這裏說一種叫映射的標記。
下面有三道例題:
T1:
首先我們要維護兩個個數組,下標i表示第i位,數組的意義是如果第i位上原本是1或者0,經過一段區間後變成了1或者0,這就是一個映射。我們設a[i]表示第i位上的1經過一段區間後變成了0或者1,則合併v的兩個兒子ls,rs的時候,v.a[i]=rs.a[ls.a[i]]。
然後用數組維護複雜度太高,我們用一個int來表示。
我們以爲例: ~。
詢問的時候,我們得到了一個映射,表示第i位上的1或者0經過指定的區間過後變成了0或者1,然後我們再DP一下。具體實現看代碼。
代碼:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#define ll long long
#define N 500005
using namespace std;
inline int Get() {
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return x;
}
char Buffer[10<<20],*T=Buffer;
char W[1000][3];
inline void Print(int x){
if(!x)return;
if(x>=1000){
Print(x/1000);
char*w=W[x%1000];
*T++=w[0];
*T++=w[1];
*T++=w[2];
}else{
Print(x/10);
*T++=x%10+'0';
}
}
inline void print(int x){
if(x==0)*T++='0';
else Print(x);
*T++='\n';
}
int n,m;
int op[N],w[N];
const int maxx=(1ll<<31)-1;
struct tree {
int l,r;
int c0,c1;
}tr[N<<2];
inline void update(int v) {
int ls=v<<1,rs=v<<1|1;
tr[v].c0=(tr[ls].c0&tr[rs].c1)|((tr[ls].c0^maxx)&tr[rs].c0);
tr[v].c1=(tr[ls].c1&tr[rs].c1)|((tr[ls].c1^maxx)&tr[rs].c0);
}
inline void build(int v,int l,int r) {
tr[v].l=l,tr[v].r=r;
if(l==r) {
if(op[l]==0) {
tr[v].c0=0;
tr[v].c1=w[l];
} else if(op[l]==1) {
tr[v].c0=w[l];
tr[v].c1=maxx;
} else {
tr[v].c0=w[l];
tr[v].c1=maxx^w[l];
}
return ;
}
int mid=l+r>>1;
build(v<<1,l,mid),build(v<<1|1,mid+1,r);
update(v);
}
inline void Modify(int v,int pos,int op,int w) {
if(tr[v].l>pos||tr[v].r<pos) return ;
if(tr[v].l==tr[v].r) {
if(op==0) {
tr[v].c0=0;
tr[v].c1=w;
} else if(op==1) {
tr[v].c0=w;
tr[v].c1=maxx;
} else {
tr[v].c0=w;
tr[v].c1=maxx^w;
}
return ;
}
Modify(v<<1,pos,op,w),Modify(v<<1|1,pos,op,w);
update(v);
}
int c0,c1;
inline void query(int v,int l,int r) {
if(tr[v].l>r||tr[v].r<l) return ;
if(l<=tr[v].l&&tr[v].r<=r) {
c0=(c0&tr[v].c1)|((c0^maxx)&tr[v].c0);
c1=(c1&tr[v].c1)|((c1^maxx)&tr[v].c0);
return ;
}
query(v<<1,l,r),query(v<<1|1,l,r);
}
int lx[31],rx[31];
int g[2];
int f[31][2][2];
inline void work(int x,int y) {
for(int i=0;i<31;i++,x>>=1) lx[i]=x&1;
for(int i=0;i<31;i++,y>>=1) rx[i]=y&1;
g[0]=c0,g[1]=c1;
memset(f,-1,sizeof(f));
f[0][0][0]=max(f[0][0][0],g[0]&1);
f[0][lx[0]<=0][0]=max(f[0][lx[0]<=0][0],g[0]&1);
f[0][0][0<=rx[0]]=max(f[0][0][0<=rx[0]],g[0]&1);
f[0][lx[0]<=0][0<=rx[0]]=max(f[0][lx[0]<=0][0<=rx[0]],g[0]&1);
f[0][0][0]=max(f[0][0][0],g[1]&1);
f[0][lx[0]<=1][0]=max(f[0][lx[0]<=1][0],g[1]&1);
f[0][0][1<=rx[0]]=max(f[0][0][1<=rx[0]],g[1]&1);
f[0][lx[0]<=1][1<=rx[0]]=max(f[0][lx[0]<=1][1<=rx[0]],g[1]&1);
for(int i=1;i<=30;i++) {
for(int j=0;j<=1;j++) {
for(int k=0;k<=1;k++) {
int a=j?lx[i]:0,b=k?rx[i]:1;
for(;a<=b;a++) {
f[i][j][k]=max(f[i][j][k],f[i-1][j&&a==lx[i]][k&&a==rx[i]]+(g[a]&(1<<i)));
}
}
}
}
cout<<f[30][1][1]<<"\n";
}
int main() {
for(int i=0;i<10;i++)
for(int j=0;j<10;j++)
for(int k=0;k<10;k++)
W[i*100+j*10+k][0]=i+'0',
W[i*100+j*10+k][1]=j+'0',
W[i*100+j*10+k][2]=k+'0';
n=Get(),m=Get();
for(int i=1;i<=n;i++) {
op[i]=Get(),w[i]=Get();
}
build(1,1,n);
int op,x,y,z;
int l,r,u,d;
while(m--) {
op=Get();
if(op==1) {
x=Get(),y=Get(),z=Get();
Modify(1,x,y,z);
} else {
l=Get(),r=Get(),u=Get(),d=Get();
c0=0,c1=maxx;
query(1,l,r);
work(u,d);
}
}
return 0;
}
T2:
【問題描述】
跳房子,是一種世界性的兒童遊戲,也是中國民間傳統的體育遊戲之一。
跳房子是在N個格子上進行的,CYJ對遊戲進行了改進,該成了跳棋盤,改進後的遊戲是在一個N行M列的棋盤上進行,並規定從第一行往上可以走到最後一行,第一列往左可以走到最後一列,反之亦然。每個格子上有一個數字。
在這個棋盤左上角(1,1)放置着一枚棋子。每次棋子會走到右、右上和右下三個方向格子中對應上數字最大一個。即任意時刻棋子都只有一種走法,不存在多個格子同時滿足條件。
現在有兩種操作:
move k 將棋子前進k步。
change a b e 將第a行第b列格子上的數字修改爲e。
請對於每一個move操作輸出棋子移動完畢後所處的位置。
【輸入】
第一行包含兩個正整數N,M(3<=N,M<=2000),表示棋盤的大小。
接下來N行,每行M個整數,依次表示每個格子中的數字a[i,j](1<= a[i,j]<=109)。
接下來一行包含一個正整數Q(1<=Q<=5000),表示操作次數。
接下來m行,每行一個操作,其中1<=a<=N,1<=b<=M,1<=k,e<=109。
【輸出】
對於每個move操作,輸出一行兩個正整數x,y,即棋子所處的行號和列號。
【輸入輸出樣例】
jump.in
4 4
1 2 9 3
3 5 4 8
4 3 2 7
5 8 1 6
4
move 1
move 1
change 1 4 100
move 1 4 2
jump.out
4 2
1 3
1 4
跳房子的過程就是一個映射,映射是可以直接合並的,並且映射是可以快速冪的。所以我們對列開一個線段樹,沒個節點中有一個數組,數組第i位表示第i行初始時就在原位置,經過了區間 後第i行在哪個位置。與上道題類似 。
然後我們處理詢問的時候從第i行第j列出發先走完m列(這樣一定會回到第j列),這樣我們又得到了一個映射,表示從第i行第j列出發,走回第j列時會到達哪一行。然後在對這個映射進行快速冪,最後不足m列的地方我們在挨着走就是了。
代碼:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#define ll long long
#define N 2005
using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
int n,m,Q;
int w[N][N];
int id[N];
struct change {
int g[N];
void Init() {for(int i=1;i<=n;i++) g[i]=i;}
}tem,h;
change operator *(const change &a,const change &b) {
for(int i=1;i<=n;i++) tem.g[i]=b.g[a.g[i]];
return tem;
}
struct tree {
int l,r;
change x;
}tr[N<<2];
void update(int v) {tr[v].x=tr[v<<1].x*tr[v<<1|1].x;}
void Get(int x,int y,int *g) {
int pos=0,nxt=y%m+1;
for(int i=-1;i<=1;i++) {
int now=x+i;
if(now<1) now=n;
else if(now==n+1) now=1;
if(w[pos][nxt]<w[now][nxt]) pos=now;
}
g[x]=pos;
}
void build(int v,int l,int r) {
tr[v].l=l,tr[v].r=r;
if(l==r) {
id[l]=v;
for(int i=1;i<=n;i++) Get(i,l,tr[v].x.g);
return ;
}
int mid=l+r>>1;
build(v<<1,l,mid),build(v<<1|1,mid+1,r);
update(v);
}
void Modify(int v,int y) {
if(tr[v].l>y||tr[v].r<y) return ;
if(tr[v].l==tr[v].r) {
for(int i=1;i<=n;i++) Get(i,y,tr[v].x.g);
return ;
}
Modify(v<<1,y),Modify(v<<1|1,y);
update(v);
}
change query(int v,int l,int r) {
if(l<=tr[v].l&&tr[v].r<=r) return tr[v].x;
int mid=tr[v].l+tr[v].r>>1;
if(mid<l) return query(v<<1|1,l,r);
else if(r<=mid) return query(v<<1,l,r);
else return query(v<<1,l,r)*query(v<<1|1,l,r);
}
change ksm(change h,int x) {
change ans;
ans.Init();
for(;x;x>>=1,h=h*h) {
if(x&1) ans=ans*h;
}
return ans;
}
int sx,sy;
void Move(int k) {
for(int i=1;i<=k;i++) {
sx=tr[id[sy]].x.g[sx];
sy=sy%m+1;
}
}
int main() {
n=Get(),m=Get();
sx=1,sy=1;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
w[i][j]=Get();
}
}
build(1,1,m);
Q=Get();
char op[10];
int x,y,z,k;
for(int i=1;i<=Q;i++) {
scanf("%s",op);
if(op[0]=='m') {
k=Get();
x=k/m,y=k%m;
h=query(1,sy,m);
if(sy>1) h=h*query(1,1,sy-1);
h=ksm(h,x);
sx=h.g[sx];
Move(y);
cout<<sx<<" "<<sy<<'\n';
} else {
x=Get(),y=Get(),z=Get();
w[x][y]=z;
y--;
if(y==0) y=m;
Modify(1,y);
}
}
return 0;
}
題目大意:一段區間每個區間有一個運算:+x,或者*x,或者^x(次方),運算優先級永遠從左到右。問給出一個初始值,經過了所有的運算之後模29393的值。
我們發現,如果沒有次方那就可以維護一個,但顯然有了次方過後就不能合併了。我們有一個很暴力的想法,開一個數組,維護0~29392的每個數經過該段區間後會變成什麼。但顯然時間和空間複雜度太高。
然後就是一般的套路:29393=71317*19。所以我們對每個質因數開一個線段樹,最後的到了答案過後再用CRT合併。
代碼:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#include<queue>
#include<iomanip>
#define ll long long
#define N 50005
#define mod 29393
using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}
int n,m;
int op[N],w[N];
ll ksm(ll t,ll x,ll p) {
ll ans=1;
for(;x;x>>=1,t=t*t%p)
if(x&1) ans=ans*t%p;
return ans;
}
struct segment {
int p;
struct tree {
int l,r;
int to[20];
}tr[N<<2];
void update(int v) {
for(int i=0;i<p;i++)
tr[v].to[i]=tr[v<<1|1].to[tr[v<<1].to[i]];
}
void build(int v,int l,int r) {
tr[v].l=l,tr[v].r=r;
if(l==r) {
for(int i=0;i<p;i++) {
if(op[l]==1) tr[v].to[i]=(i+w[l])%p;
else if(op[l]==2) tr[v].to[i]=i*w[l]%p;
else tr[v].to[i]=ksm(i,w[l],p);
}
return ;
}
int mid=l+r>>1;
build(v<<1,l,mid),build(v<<1|1,mid+1,r);
update(v);
}
void Modify(int v,int pos) {
if(tr[v].l>pos||tr[v].r<pos) return ;
if(tr[v].l==tr[v].r) {
int l=tr[v].l;
for(int i=0;i<p;i++) {
if(op[l]==1) tr[v].to[i]=(i+w[l])%p;
else if(op[l]==2) tr[v].to[i]=i*w[l]%p;
else tr[v].to[i]=ksm(i,w[l],p);
}
return ;
}
Modify(v<<1,pos),Modify(v<<1|1,pos);
update(v);
}
}T[5];
void exgcd(ll a,ll b,ll &x,ll &y) {
if(!b) {
x=1,y=0;
return ;
}
exgcd(b,a%b,y,x);
y=y-a/b*x;
}
int CRT(int p,int k) {
ll x,y;
exgcd(mod/p,p,x,y);
x*=k;
x=(x%p+p)%p;
return x*mod/p;
}
int main() {
n=Get(),m=Get();
char cm;
for(int i=1;i<=n;i++) {
while(cm=getchar(),cm!='^'&&cm!='*'&&cm!='+');
if(cm=='+') op[i]=1;
else if(cm=='*') op[i]=2;
else op[i]=3;
w[i]=Get();
}
T[1].p=7;
T[2].p=13;
T[3].p=17;
T[4].p=19;
for(int i=1;i<=4;i++) T[i].build(1,1,n);
int x,x1,x2,x3,x4;
int q;
while(m--) {
q=Get();
if(q==1) {
x=Get();
x1=T[1].tr[1].to[x%T[1].p];
x2=T[2].tr[1].to[x%T[2].p];
x3=T[3].tr[1].to[x%T[3].p];
x4=T[4].tr[1].to[x%T[4].p];
ll ans=0;
ans+=CRT(T[1].p,x1);
ans+=CRT(T[2].p,x2);
ans+=CRT(T[3].p,x3);
ans+=CRT(T[4].p,x4);
cout<<ans%mod<<"\n";
} else {
x=Get();
while(cm=getchar(),cm!='^'&&cm!='*'&&cm!='+');
if(cm=='+') op[x]=1;
else if(cm=='*') op[x]=2;
else op[x]=3;
w[x]=Get();
for(int i=1;i<=4;i++) T[i].Modify(1,x);
}
}
return 0;
}