题解 P1047 【校门外的树】

Juan_feng

2018-10-12 18:22:39

Solution

为何要用树状数组?只是为了炫耀你们的学识吗?用模拟难道不是更好吗? ——为何要用模拟?只是为了炫耀你们的学识吗?用线段树难道不是更好吗? ——为何要用线段树?只是为了炫耀你们的学识吗?用分块难道不是更好吗? ——为何要用分块?只是为了炫耀你们的学识吗?用**珂朵莉树**难道不是更好吗? (以上改编自本题一题解的评论qwq) ------------ ### 小蒟蒻终于有机会写一道这么难的题的题解了,So exciting! 看了上面的一段话,各位巨佬一定已经明白小蒟蒻这篇题解想讲的是什么了,**但是** ## 珂朵莉树是个什么东西? ![](https://cdn.luogu.com.cn/upload/pic/37472.png) #### 肯定有人想问这个问题,小蒟蒻这里先不说,各位暂且看看小蒟蒻做这道题的心路历程QωQ ~~在刚学OI的时候,~~小蒟蒻最开始自然是用**模拟**做的,直接N方暴力覆盖,最后再去查询qwq 之后小蒟蒻接触了**线段树**,感觉线段树这东西好好用呀,~~手里拿着锤子看什么都是钉子~~,结果就重新发现了这道神题,一开始还调了不少时间,后来发现其实就是个线段树的板子qwq,初始值都赋为1,update赋值为0,最后query求和一下就完事了(这道题因为是统计全部区间可以直接输出ans[1]) 之后小蒟蒻有去学了**分块**,~~感觉分块这东西也好好用呀~~,把数列分成根号n块,对于散块直接暴力修改,整块打个tag,同时要维护一个ans数组,保存每个块内的答案。查询因为是从0~n,一定都是整块,小蒟蒻就偷了个懒,直接把所有的ans加起来了。 (**以上三种方法的代码在题解最后给出) ** ### 好了,终于要进入今天的正题了! 观察题目:区间赋值为0 ### 选择珂朵莉树 ![](https://cdn.luogu.com.cn/upload/pic/37474.png) 简单介绍一下,珂朵莉树是一种基于set的暴力数据结构 珂朵莉树的关键在于推平一段区间,即把一段区间的数变的一样(似乎所有珂朵莉树的讲解里面都说了这一句话) 对每一个点建立一个集合 当需要修改的时候,就把要修改区间分成两部分,一部分是需要修改的,一部分是不需要修改的,返回需要修改的地址; 然后把这一段区间内所有的集合都删掉,用一个大集合代替之就可以了。 那么代码如下: **先上珂朵莉树:** ``` #include<iostream> #include<cstdio> #include<cstring> #include<set> #include<algorithm> #define re register #define FOR(i,l,r) for(re int i=l;i<=r;++i) #define IT set<node>::iterator using namespace std; int n,m,x,y; inline void in(re int &x){ x=0;char c=getchar(); while(c<'0'||c>'9'){ c=getchar(); } while(c<='9'&&c>='0'){ x=(x<<1)+(x<<3)+(c^'0'); c=getchar(); } } inline void out(re int a){ if(a>=10)out(a/10); putchar(a%10+'0'); } struct node{ int l,r; mutable int v; node(int L,int R=-1,int V=0):l(L),r(R),v(V) {} bool operator <(const node &o)const{ return l<o.l; } }; set<node> s; inline IT split(re int pos){ IT it=s.lower_bound(node(pos)); if(it!=s.end()&&it->l==pos) return it; --it; int L=it->l; int R=it->r; int V=it->v; s.erase(it); s.insert(node(L,pos-1,V)); return s.insert(node(pos,R,V)).first; } inline void assign_val(re int l,re int r,re int val=0){ IT itr=split(r+1),itl=split(l); s.erase(itl,itr); s.insert(node(l,r,val)); } inline int query(re int l,re int r){ int res=0; IT itr=split(r+1),itl=split(l); for(;itl!=itr;++itl) res+=(itl->r-itl->l+1)*(itl->v); return res; } int main(){ in(n),in(m); s.insert(node(0,n,1)); FOR(i,1,m){ in(x),in(y); assign_val(x,y,0); } out(query(0,n)); puts(""); } ``` ### 然后是之前提到的三种方法: **code1 模拟:** ``` #include<iostream> #include<cstring> #include<cstdio> #define re register #define FOR(i,l,r) for(re int i=l;i<=r;++i) using namespace std; int a[200001],i,h1,h2,s=0,m,l,j; inline void in(re int &x){ x=0;char c=getchar(); while(c<'0'||c>'9'){ c=getchar(); } while(c<='9'&&c>='0'){ x=(x<<1)+(x<<3)+(c^'0'); c=getchar(); } } inline void out(re int a){ if(a>=10)out(a/10); putchar(a%10+'0'); } int main(){ in(l),in(m); FOR(i,1,m){ in(h1),in(h2); FOR(j,h1,h2) a[j]=1; } FOR(i,0,l) if(a[i]==0) ++s; out(s); puts(""); } ``` **code2 线段树:** ``` #include<iostream> #include<cstdio> #include<cstring> #define re register #define FOR(i,l,r) for(re int i=l;i<=r;++i) using namespace std; int a[100001],ans[100001],tag[100001],m,n,k,l,t,cnt,x,y; inline int in(){ char ch; int a=0; while(!(((ch=getchar())>='0')&&(ch<='9'))); a*=10;a+=ch-'0'; while(((ch=getchar())>='0')&&(ch<='9'))a*=10,a+=ch-'0'; return a; } inline void out(int a){ if(a>=10)out(a/10); putchar(a%10+'0'); } inline int ls(int ss){ return ss<<1; } inline int rs(int ss){ return (ss<<1)|1; } inline void push_up(int k){ ans[k]=ans[ls(k)]+ans[rs(k)]; } inline void push_down(int i){ if(tag[i]){ tag[i]=0; tag[ls(i)]=1; tag[rs(i)]=1; ans[ls(i)]=0; ans[rs(i)]=0; } } inline void build(int p,int l,int r){ if(l==r){ ans[p]=1; return; } int mid=(l+r)>>1; build(ls(p),l,mid); build(rs(p),mid+1,r); push_up(p); } inline void update(int nl,int nr,int l,int r,int p){ if(nl<=l&&r<=nr){ tag[p]=1; ans[p]=0; return; } push_down(p); int mid=(l+r)>>1; if(nl<=mid) update(nl,nr,l,mid,ls(p)); if(nr>mid) update(nl,nr,mid+1,r,rs(p)); push_up(p); } int main(){ n=in(),m=in(); build(1,1,n+1); FOR(i,1,m){ x=in(),y=in(); update(x+1,y+1,1,n+1,1); } out(ans[1]); puts(""); } ``` **code3 分块:** ``` #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define maxn 500010 #define re register #define FOR(i,l,r) for(re int i=l;i<=r;++i) using namespace std; int n,m,c,r,t,x,y,z,sq,anss; int a[maxn],b[maxn],tag[maxn],ans[maxn]; inline void in(int &x){ x=0;char c=getchar(); while(c<'0'||c>'9'){ c=getchar(); } while(c<='9'&&c>='0'){ x=(x<<1)+(x<<3)+(c^'0'); c=getchar(); } } inline void out(int a){ if(a>=10)out(a/10); putchar(a%10+'0'); } void doit(int x,int y){ if(tag[b[x]]!=1) FOR(i,x,min(y,b[x]*sq)) ans[b[x]]-=a[i],a[i]=0; if(b[x]!=b[y]&&tag[b[y]]!=1) FOR(i,(b[y]-1)*sq+1,y) ans[b[y]]-=a[i],a[i]=0; FOR(i,b[x]+1,b[y]-1) tag[i]=1,ans[i]=0; } int main(){ in(n),in(m); sq=sqrt(n); FOR(i,0,n) a[i]=1,b[i]=(i-1)/sq+1,++ans[b[i]]; FOR(i,1,m){ in(x),in(y); doit(x,y); } FOR(i,1,b[n]) anss+=ans[i]; out(anss); puts(""); } ``` **小蒟蒻的题解也就到此结束啦qwq** **如果有什么意见或建议的话请和小蒟蒻私信呐qwq** ### 最后祝大家NOIP RP++!