1. 程式人生 > 實用技巧 >2020 江蘇省賽A Array 線段樹的區間對映 or 線段樹合併

2020 江蘇省賽A Array 線段樹的區間對映 or 線段樹合併

這題還是很有意思的
我們觀察題目發現 3操作很難維護(對於線段樹維護冪次方和 一般只能很低的冪 比如二次方 三次方 這也很麻煩了)
但是我們發現 p特別小 只有30
於是我們可以維護一個對映
最常用的方法就是 開30棵線段樹 然後用懶惰標記維護一個對映
例如 1再經過某個操作之後變成了3 那麼tag[id][1]就是3
注意在下推標記的時候 要考慮標記的迭代
這是第一種做法 耗時2636ms

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+100;
int sum[32][N<<2],lazy[32][N<<2];
int phi,p,gn;
int num[32],ct[32],val[N];
int eular(int n){
	int ans=n;
	for(int i = 2; i*i <= n; i++){
		if(n%i==0){
			ans=ans/i*(i-1);
			while(n%i==0) n/=i;
		}
	}
	if(n>1) ans=ans/n*(n-1);
	return ans;
}
int e_qpow(int a,int b){
	if(__gcd(a,p)==1){
		b = b%phi;
	}else if(b >= phi){
		b = b%phi+phi;
	}
	int ans=1;
	while(b){
		if(b&1) ans=(ans*a)%p;
		a=(a*a)%p;
		b>>=1;
	}
	return ans;
}
#define ls id<<1
#define rs ls|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define mid (l+r>>1)
void pushup(int id){
	for(int i = 0; i < p; i++)
	sum[i][id]=(sum[i][ls]+sum[i][rs]);
}
void pushdown(int id){
	for(int i = 0; i < p; i++){
		num[i]=sum[i][ls];
		sum[i][ls]=0;
	}
	for(int i = 0; i < p; i++){
		sum[lazy[i][id]][ls]+=num[i];
	}
	
	for(int i = 0; i < p; i++){
		num[i]=sum[i][rs];
		sum[i][rs]=0;
	}
	for(int i = 0; i < p; i++){
		sum[lazy[i][id]][rs]+=num[i];
	}
	
	for(int i = 0; i < p; i++){
		lazy[i][ls]=lazy[lazy[i][ls]][id];
		lazy[i][rs]=lazy[lazy[i][rs]][id];
	}
	
	for(int i = 0; i < p; i++)
		lazy[i][id]=i;

}
void build(int id,int l,int r){
	if(l==r){
		scanf("%d",&val[l]);
		sum[val[l]%p][id]=1;
		return;
	}
	for(int i = 0; i < p; i++)
	lazy[i][id]=i;
	build(lson);build(rson);
	pushup(id);
}
int get(int op,int x,int v){
	if(op==1) return (x+v)%p;
	else if(op==2) return 1ll*x*v%p;
	else return e_qpow(x,v)%p;
}
void update(int id,int l,int r,int L,int R,int op,int v){
	if(L<=l&&R>=r){
		for(int i = 0; i < p; i++){
			num[i]=sum[i][id];
			sum[i][id]=0;
		}
		for(int i = 0; i < p; i++){
			int x = get(op,i,v);
			sum[x][id]+=num[i];
			lazy[i][id]=get(op,lazy[i][id],v);
		}
		return;
	}
	pushdown(id);
	if(L<=mid) update(lson,L,R,op,v);
	if(R>mid) update(rson,L,R,op,v);
	pushup(id);	
}
void query(int id,int l,int r,int L,int R){
	if(L<=l&&R>=r){
		for(int i = 0; i < p; i++) 
		ct[i]+=sum[i][id];
		return;
	}
	pushdown(id);
	if(L<=mid) query(lson,L,R);
	if(R>mid) query(rson,L,R);
}
int main(){
	//for(int i = 2; i <= 30; i++) printf("i=%d eular=%d\n",i,eular(i));
	scanf("%d%d",&gn,&p);
	phi=eular(p);
	build(1,1,gn);
	int q;
	scanf("%d",&q);
	while(q--){
		int op,l,r,k;
		scanf("%d%d%d%d",&op,&l,&r,&k);
		if(op<=3){
			update(1,1,gn,l,r,op,k);
		}else if(op==4){
			memset(ct,0,sizeof(ct));
			query(1,1,gn,l,r);
			int ans = 0;
			for(int i = 0; i < p; i++){
				ans=(ans+e_qpow(i,k)*ct[i])%p;
			}
			//printf("%d\n",ans);
		}else if(op==5){
			memset(ct,0,sizeof(ct));
			query(1,1,gn,l,r);
			int ans = 1;
			for(int i = 0; i < p; i++){
				ans=(ans*e_qpow(i,ct[i]))%p;
			}
		//	printf("%d\n",ans);
		}
		memset(ct,0,sizeof(ct));
		query(1,1,gn,1,gn);
		for(int i = 0; i < p; i++){
			printf("i=%d sum=%d\n",i,ct[i]);
		}
	}
	return 0;
}

第二種做法 是由CF991G Mass Change Queries 啟發我的
這題題意很簡單 就是把區間裡面為x的數轉換為y 我尋思這不就是一個對映嗎
然後發現這題有兩種題解 其一是上述那種方法 跑的很慢 其二就是線段樹合併了 這東西是真的強 而且跑的很快
線段樹合併和fhq平衡樹很相似(具體我也不知道啥時候用線段樹合併 啥時候用fhq fhq感覺處理前後驅問題更好)
此處用線段樹合併可以很快的完成對映操作
對於每一個值 我們維護一棵線段樹
然後1,2,3號操作都是對映操作 我們求出每個值的對映
把對應的線段樹分裂開來 最後再合併在一起
速度很快 只跑了 748ms 在cf上rk1

#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+100; 
int phi,p,gn,q;
struct node{
	int l,r,v;
}t[N*50];
int rt[40],nrt[40],mp[40];
int plot[N*50],cnt,tot;
int eular(int n){
	int ans=n;
	for(int i = 2; i*i <= n; i++){
		if(n%i==0){
			ans=ans/i*(i-1);
			while(n%i==0) n/=i;
		}
	}
	if(n>1) ans=ans/n*(n-1);
	return ans;
}
int e_qpow(int a,int b){
	if(__gcd(a,p)==1){
		b = b%phi;
	}else if(b >= phi){
		b = b%phi+phi;
	}
	int ans=1;
	while(b){
		if(b&1) ans=(ans*a)%p;
		a=(a*a)%p;
		b>>=1;
	}
	return ans;
}
inline int newnode(){
	return cnt?plot[cnt--]:++tot;
}
void del(int x){
	t[x].l=t[x].r=t[x].v=0;
	plot[++cnt]=x;
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	t[x].v+=t[y].v;
	t[x].l=merge(t[x].l,t[y].l);
	t[x].r=merge(t[x].r,t[y].r);
	del(y);
	return x;
}
void pushup(int o){
	t[o].v=t[t[o].l].v+t[t[o].r].v;
}
void update(int &o,int l,int r,int pos){
	if(!o) o=newnode();
	t[o].v++;
	if(l==r) return;
	int mid = l+r>>1;
	if(pos<=mid) update(t[o].l,l,mid,pos);
	else update(t[o].r,mid+1,r,pos);
}
void change(int &x,int &y,int l,int r,int L,int R){
	if(!x) return;
	if(L<=l&&R>=r){
		y=merge(x,y);x=0;
		return;
	}
	if(!y) y=newnode();
	int mid = l+r>>1;
	if(L<=mid) change(t[x].l,t[y].l,l,mid,L,R);
	if(R>mid) change(t[x].r,t[y].r,mid+1,r,L,R);
	pushup(x);
	pushup(y);
} 
void getinfo(int T,int l,int r,int k){
	for(int i = 0; i < p; i++){
		if(T==1) mp[i]=(i+k)%p;
		else if(T==2) mp[i]=(1ll*i*k)%p;
		else mp[i]=e_qpow(i,k)%p;
		nrt[i]=0;
	}
	for(int i = 0; i < p; i++){
		if(i==mp[i]) continue;
		change(rt[i],nrt[mp[i]],1,gn,l,r);
	}
	for(int i = 0; i < p; i++){
		rt[i]=merge(rt[i],nrt[i]);
	}
}
int query(int o,int l,int r,int L,int R){
	if(!o) return 0;
	if(L<=l&&R>=r) return t[o].v;
	int mid = l+r>>1,ret = 0;
	if(L<=mid) ret+=query(t[o].l,l,mid,L,R);
	if(R>mid) ret+=query(t[o].r,mid+1,r,L,R);
	return ret;
}
int main(){
	scanf("%d%d",&gn,&p);
	phi=eular(p);
	for(int i = 1; i <= gn; i++){
		int v;
		scanf("%d",&v);
		update(rt[v%p],1,gn,i);
	}
	scanf("%d",&q);
	for(int i = 1; i <= q; i++){
		int T,l,r,k;
		scanf("%d%d%d%d",&T,&l,&r,&k);
		if(T<=3){
			getinfo(T,l,r,k);
		}else if(T==4){
			int ans=0;
			for(int i = 0; i < p; i++){
				ans=(ans+e_qpow(i,k)*query(rt[i],1,gn,l,r)%p)%p;
			}
			printf("%d\n",ans);
		}else{
			int ans=1;
			for(int i = 0; i < p; i++){
				ans=(ans*e_qpow(i,query(rt[i],1,gn,l,r)))%p;
			}
			printf("%d\n",ans); 
		}
		
	}
	return 0;
}