1. 程式人生 > >【NOIP2018模擬11.01】樹

【NOIP2018模擬11.01】樹

題目

描述

在這裡插入圖片描述

題目大意

維護一個序列,支援三種操作: 1、修改一段區間,將這段區間內的所有數都andand一個數。 2、詢問區間和。 3、詢問區間兩兩相加的平方和。 N10000N\leq 10000

思路

顯然是一道資料結構題。 毋庸置疑的,這絕對是一棵線段樹。 第三個操作還是比較簡單的: (ai+aj)2=ai2+aj2+2aiaj=2lenai2+2aiaj=2lenai2+2(ai)2\sum{(a_i+a_j)^2} \\ =\sum{a_i^2+a_j^2+2a_ia_j}\\ =2*len*\sum{a_i^2}+2\sum{a_ia_j}\\ =2*len*\sum{a_i^2}+2\left(\sum{a_i}\right)^2

所以只需要維護區間和還有區間平方和。 這題中,最討厭的就是修改操作,好端端的,幹嘛要來個位運算! 所以我就是著將所有的位分開來。 然而,搞不了第三個詢問…… 想了半天后棄療看題解。

正解

這題正解就是直接暴力,沒錯,就是暴力。 對於線段樹的每一個節點,維護一個值表示這段區間內或起來的和。 在修改的時候,我們就可以通過這個東西來判斷這個區間裡面是否有需要修改的數。 如果有,就繼續往下,將它揪出來,暴力修改。 然後?然後就沒了啊…… 聽起來這個方法的時間很詭異,實際上—— 對於N

N個點,每個點一共有3030個位,又因為修改過一個位之後就再也不可能修改這個位,所以,頂多修改30N30N次。 乘上線段樹的高度就是30NlgN30N\lg N次。 所以時間複雜度是O(NlgNlg109)O(N\lg N\lg 10^9)

程式碼

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100000
#define BIt 30
#define mo 998244353
inline long long pow2(long long x){return x*x;}
int n;
int a[N+1];
struct Ret{//分別是區間和、區間平方和
	long long sum;
	int sum2;
};
struct Node{
	int o;//表示or值
	Ret s;
} d[N*4+1];
void init(int,int,int);
void find(int,int,int,int,int,int);//找被修改區間完全覆蓋的點
void change(int,int,int,int);
inline Ret operator+(const Ret &a,const Ret &b){
	return {a.sum+b.sum,(a.sum2+b.sum2)%mo};
}
Ret query(int,int,int,int,int);
int main(){
	freopen("seg.in","r",stdin);
	freopen("seg.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	init(1,1,n);
	int T;
	scanf("%d",&T);
	while (T--){
		int op;
		scanf("%d",&op);
		if (op==1){
			int l,r,x;
			scanf("%d%d%d",&l,&r,&x);
			find(1,1,n,l,r,x);
		}
		else if (op==2){
			int l,r;
			scanf("%d%d",&l,&r);
			printf("%lld\n",query(1,1,n,l,r).sum);
		}
		else{
			int l,r;
			scanf("%d%d",&l,&r);
			Ret res=query(1,1,n,l,r);
			printf("%lld\n",(((long long)res.sum2*(r-l+1)%mo+pow2(res.sum%mo)%mo)<<1)%mo);
		}
	}
	return 0;
}
void init(int k,int l,int r){
	if (l==r){
		d[k].s.sum=d[k].o=a[l];
		d[k].s.sum2=(long long)a[l]*a[l]%mo;
		return;
	}
	int mid=l+r>>1;
	init(k<<1,l,mid);
	init(k<<1|1,mid+1,r);
	d[k].o=d[k<<1].o|d[k<<1|1].o;
	d[k].s=d[k<<1].s+d[k<<1|1].s;
}
void find(int k,int l,int r,int st,int en,int x){
	if (st<=l && r<=en){
		change(k,l,r,x);
		return;
	}
	int mid=l+r>>1;
	if (st<=mid)
		find(k<<1,l,mid,st,en,x);
	if (mid<en)
		find(k<<1|1,mid+1,r,st,en,x);
	d[k].o=d[k<<1].o|d[k<<1|1].o;
	d[k].s=d[k<<1].s+d[k<<1|1].s;
}
void change(int k,int l,int r,int x){
	if ((d[k].o&x)==d[k].o)
		return;
	if (l==r){
		d[k].s.sum=d[k].o&=x;
		d[k].s.sum2=(long long)d[k].o*d[k].o%mo;
		return;
	}
	int mid=l+r>>1;
	change(k<<1,l,mid,x);
	change(k<<1|1,mid+1,r,x);
	d[k].o=d[k<<1].o|d[k<<1|1].o;
	d[k].s=d[k<<1].s+d[k<<1|1].s;
}
Ret query(int k,int l,int r,int st,int en){
	if (st<=l && r<=en)
		return d[k].s;
	int mid=l+r>>1;
	Ret res={0,0};
	if (st<=mid)
		res=res+query(k<<1,l,mid,st,en);
	if (mid<en)
		res=res+query(k<<1|1,mid+1,r,st,en);
	return res;
}

總結

資料結構的時間複雜度,不應該只看他每次操作的複雜度,還要看看總共最多的複雜度。尤其是類似一次性修改的東西(就是這個資料修改過一次之後就不能再修改了)。 話說,我突然想起以前的一道題目: 有一道資料結構提,正解是分塊的根號做法,題解說,線段樹不能做…… 我堅持用線段樹,最終AC了那題,log做法,吊打標算。風光了一時 當時用的也差不多是這樣的思想……