【NOIP2018模擬11.01】樹
阿新 • • 發佈:2018-12-19
題目
描述
題目大意
維護一個序列,支援三種操作: 1、修改一段區間,將這段區間內的所有數都一個數。 2、詢問區間和。 3、詢問區間兩兩相加的平方和。
思路
顯然是一道資料結構題。 毋庸置疑的,這絕對是一棵線段樹。 第三個操作還是比較簡單的: 所以只需要維護區間和還有區間平方和。 這題中,最討厭的就是修改操作,好端端的,幹嘛要來個位運算! 所以我就是著將所有的位分開來。 然而,搞不了第三個詢問…… 想了半天后棄療看題解。
正解
這題正解就是直接暴力,沒錯,就是暴力。 對於線段樹的每一個節點,維護一個值表示這段區間內或起來的和。 在修改的時候,我們就可以通過這個東西來判斷這個區間裡面是否有需要修改的數。 如果有,就繼續往下,將它揪出來,暴力修改。 然後?然後就沒了啊…… 聽起來這個方法的時間很詭異,實際上—— 對於個點,每個點一共有個位,又因為修改過一個位之後就再也不可能修改這個位,所以,頂多修改次。 乘上線段樹的高度就是次。 所以時間複雜度是
程式碼
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做法,吊打標算。風光了一時
當時用的也差不多是這樣的思想……