zoukankan      html  css  js  c++  java
  • 【NOIP2018模拟11.01】树

    题目

    描述

    在这里插入图片描述

    题目大意

    维护一个序列,支持三种操作:
    1、修改一段区间,将这段区间内的所有数都andand一个数。
    2、询问区间和。
    3、询问区间两两相加的平方和。
    N10000Nleq 10000


    思路

    显然是一道数据结构题。
    毋庸置疑的,这绝对是一棵线段树。
    第三个操作还是比较简单的:
    (ai+aj)2=ai2+aj2+2aiaj=2lenai2+2aiaj=2lenai2+2(ai)2sum{(a_i+a_j)^2} \ =sum{a_i^2+a_j^2+2a_ia_j}\ =2*len*sum{a_i^2}+2sum{a_ia_j}\ =2*len*sum{a_i^2}+2left(sum{a_i} ight)^2
    所以只需要维护区间和还有区间平方和。
    这题中,最讨厌的就是修改操作,好端端的,干嘛要来个位运算!
    所以我就是着将所有的位分开来。
    然而,搞不了第三个询问……
    想了半天后弃疗看题解。


    正解

    这题正解就是直接暴力,没错,就是暴力。
    对于线段树的每一个节点,维护一个值表示这段区间内或起来的和。
    在修改的时候,我们就可以通过这个东西来判断这个区间里面是否有需要修改的数。
    如果有,就继续往下,将它揪出来,暴力修改。
    然后?然后就没了啊……
    听起来这个方法的时间很诡异,实际上——
    对于NN个点,每个点一共有3030个位,又因为修改过一个位之后就再也不可能修改这个位,所以,顶多修改30N30N次。
    乘上线段树的高度就是30NlgN30Nlg N次。
    所以时间复杂度是O(NlgNlg109)O(Nlg Nlg 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
    ",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
    ",(((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做法,吊打标算。风光了一时
    当时用的也差不多是这样的思想……

  • 相关阅读:
    oracle维护表空间和数据文件
    IOS 应用的架构解析
    html5之拖放简单效果
    跟Google学习Android开发-起始篇-与其它应用程序交互(1)
    淘宝服务市场 淘宝订单同步方案
    论文阅读笔记
    页面爬虫(获取其他页面HTML)加载到自己页面
    由href return false 来看阻止默认事件
    Delete it
    Mac上利用Eclipse编译Cocos2d-x
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145262.html
Copyright © 2011-2022 走看看