常见的线段树是将左右两个区间的标记合并。然而有些时候传统的标记无法合并(比如不满足结合律的运算),这时候就需要用特殊的标记。这里说一种叫映射的标记。
下面有三道例题:
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;
}