1. 程式人生 > >HDU 6356 Glad You Came 區間操作分解,倍增

HDU 6356 Glad You Came 區間操作分解,倍增

剛開題面 式子有點多...不要慌..只是生成操作的資料.(防止因為讀入超時.)

題意:長度為n的序列a,初始為0,  m次操作(l[i],r[i],v[i]) j=[l[i],r[i]] 若a[j]<v[i] 則令a[j]=v[i].
n<=1e5,m<=5e6. 輸出m次操作後,序列a的異或和.


若兩個操作的區間都相同,顯然其作用的只有v值較大的那一個.
另d=log2(r-l+1).則可以將一個詢問拆成兩個(l,r,v) -> (l,l+2^d-1),(r-2^d,r).

現在所有操作的區間長度都是2的冪次.
繼續將操作區間分解,長度從大到小不斷往下拆,例如長度為2^i分解成兩個2^i-1區間.直到分解到2^0即可.O(m+nlogn).

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned int ui;
const int N=1e5+5,M=5e6+5;
int T,n,m,lg[N];
ui a[20][N];
ui f[M*3];
ui x,y,z;
ui rng(){
	x=x^(x<<11);
	x=x^(x>>4);
	x=x^(x<<5);
	x=x^(x>>14);
	ui w=x^(y^z);
	x=y;
	y=z;
	z=w;
	return z;
}
int main(){
	scanf("%d",&T);
	lg[2]=1;
	for(int i=3;i<N;i++)	lg[i]=lg[i>>1]+1;
	while(T--){
		memset(a,0,sizeof(a));
		scanf("%d%d%u%u%u",&n,&m,&x,&y,&z);
		for(int i=1;i<=3*m;i++)	
			f[i]=rng();
		int mx=0;
		for(int i=1;i<=m;i++){
			int l=min(f[3*i-2]%n,f[3*i-1]%n)+1;
			int r=max(f[3*i-2]%n,f[3*i-1]%n)+1;
			ui v=f[3*i]%(1<<30);
			int d=lg[r-l+1];
			a[d][l]=max(a[d][l],v);
			a[d][r-(1<<d)+1]=max(a[d][r-(1<<d)+1],v);
			mx=max(d,mx);
		}
		for(int k=mx;k>=1;k--){
			for(int i=1;i<=n;i++){
				if(i+(1<<k)-1>n)	break;
				int d=1<<(k-1);
				a[k-1][i]=max(a[k-1][i],a[k][i]);
				a[k-1][i+d]=max(a[k-1][i+d],a[k][i]);
			}
		}
		ll res=0;
		for(int i=1;i<=n;i++)
			res^=1ll*a[0][i]*i;
		cout<<res<<'\n';
	}
	return 0;
}

線段樹維護區間的最小值.若該區間最小值<v[i] 則到該區間內暴力更新.也能抖過..