【POJ 2528 Mayor's posters】
每个海报会覆盖一段连续的区间,所以这是个区间覆盖问题,可以用线段树。
但硬上nlogm虽然不会tle,但会mle,所以要离散化。
在[1,10000000]的这个瓷砖里,只有10000级别的修改,我们可以想象出整个区间可以被划分为多个被cover情况【一致】的小区间。那什么样的小区间被cover情况一致呢,所有端点排序后相邻端点构成的区间被cover情况是一致的,为什么呢?因为每次的覆盖都必定是两两端点间的。
这样的话每次cover就是cover这个海报start所代表的区间到end所代表的区间。
【注意要从后往前贴海报】
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define MAXN 20010 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; int l[MAXN],r[MAXN],a[MAXN]; map<int,int> mp; struct node{ int l,r; bool covered; int d;//有没有被cover,有多少部分被cover不重要,只关心这个区间有没有被【完全】cover node *ls,*rs; }pool[MAXN*4]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->covered=false; p->d=0; if( l==r ) return p; p->ls = buildT(l,(l+r)/2); p->rs = buildT( (l+r)/2+1,r ); return p; } void update(node* p,int l,int r){//覆盖上这个区间 if( p->l==l && p->r==r ){ p->d=1; return; } int mid=(p->l+p->r)/2; if( mid>=r ) update(p->ls,l,r); else if( l>mid ) update(p->rs,l,r); else{ update(p->ls,l,mid); update(p->rs,mid+1,r); } p->d = p->d | ( p->ls->d&p->rs->d); } bool query(node* p,int l,int r){//询问这个区间有没有被完全覆盖 if( p->l==l && p->r==r ) return p->d; if( p->d ) return true;//p->d为1的话,省去了pushdown == 况且p->d为0的话也不能pushdown int mid=(p->l+p->r)/2; if( mid>=r ) return query(p->ls,l,r); else if( l>mid ) return query(p->rs,l,r); return query(p->ls,l,mid)&query(p->rs,mid+1,r); } int main(){ //ios::sync_with_stdio(false); int t; scanf("%d",&t); while(t--){ int n; scanf("%d",&n); int cnt=0,id=0; for(int i=1;i<=n;i++){ scanf("%d%d",&l[i],&r[i]); a[++id]=l[i]; a[++id]=r[i]; } sort(a+1,a+1+id); int num=unique(a+1,a+1+id)-(a+1); for(int i=1;i<=num;i++) mp[ a[i] ] = i; top=0; node* root = buildT(1,num); for(int i=n;i>=1;i--){ if( !query(root,mp[ l[i] ],mp[ r[i] ]) ) cnt++; update(root,mp[ l[i] ],mp[ r[i] ]); } printf("%d ",cnt); } return 0; }
【HDU 2698 Just a Hook】
对于线段树我想的是每个node对应一个区间 [l,r] ,并且会用d存一个这个区间里所记录的值,那这涉及到一个问题是对于终止结点该怎么修改,修改后要怎么反馈到上层结点(即push up)。
然后更新d值我会从两个方面考虑,一就是根据题意随便搞,二是考虑下贡献。对于这题的话就是随便搞,然后记得加一个tag(延迟更新)记录这个区间是什么颜色,以后询问或更新以下的区间的话要pushdown下去。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define MAXN 200010 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int l,r; int d,tag; node *ls,*rs; }pool[4*MAXN]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->tag=0;//以下的都是tag类型的hook if( l==r ){ p->d=1; return p; } int mid = (l+r)/2; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); p->d = p->ls->d+p->rs->d; return p; } int getData(node* p){ if(p->tag==0) return p->d; return (p->r-p->l+1)*p->tag; } void pushdown(node* p){ if(p->tag==0) return; p->ls->tag = p->tag; p->rs->tag = p->tag; p->d = getData(p); p->tag=0; } void update(node* p,int l,int r,int type){ if( p->l==l && p->r==r ){ p->tag=type; return; } pushdown(p);//顺序重要 int mid=(p->r+p->l)/2; if( r<=mid ) update(p->ls,l,r,type); else if( l>mid ) update(p->rs,l,r,type); else{ update(p->ls,l,mid,type); update(p->rs,mid+1,r,type); } p->d = getData(p->ls)+getData(p->rs); } int query(node* p,int l,int r){ if( p->l==l && p->r==r ) return getData(p); pushdown(p); int mid = (p->l+p->r)/2; if( r<=mid ) return query(p->ls,l,r); else if( l>mid ) return query(p->rs,l,r); return query(p->ls,l,mid)+query(p->rs,mid+1,r); } int main(){ //ios::sync_with_stdio(false); int t; scanf("%d",&t); for(int i=1;i<=t;i++){ int n,q; scanf("%d%d",&n,&q); top=0; node* root = buildT(1,n); for(int j=1;j<=q;j++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); update(root,x,y,z); } printf("Case %d: The total value of the hook is %d. ",i,query(root,1,n)); } return 0; }
【ZOJ 1610 Count the Colors】
这题算是我很喜欢的一道线段树题了, 没有被虐就没有热爱。感觉这题好是好在在线段树里加入了【暴力】,就很棒
原本想的是logn回答的话,那就是要用logn级别个终止结点拼凑出答案,那每个结点岂不是都要存一下有哪些颜色段,而且颜色段分别是什么?但这样太复杂了
需要敏锐地捕捉到对于每个data set只会有一次询问,那么O(n)的复杂度处理询问是可以接受的喂!
那么实现的话就是询问的时候把所有颜色都推到【叶子节点】看叶子结点是什么颜色就好了啊,就这么做完了。
而且注意到这里有个好处是由于dfs那么叶子节点的访问顺序一定是[1,1] , [2,2] , [3,3] , ... , [n,n],借此可以算出整个颜色段
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define MAXN 200010 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int l,r; int tag; node *ls,*rs; }pool[4*MAXN]; int ans[MAXN]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->tag=-1; if( l==r ) return p; int mid = (l+r)>>1; p->ls = buildT( l,mid ); p->rs = buildT( mid+1,r ); return p; } void pushdown(node* p){ if( p->tag==-1 ) return; p->ls->tag=p->tag; p->rs->tag=p->tag; p->tag=-1; } void update(node* p,int l,int r,int dx){ if( p->l==l && p->r==r ){ p->tag = dx; return; } int mid = (p->l+p->r)>>1; pushdown(p); if( r<=mid ) update(p->ls,l,r,dx); else if( l>mid ) update(p->rs,l,r,dx); else{ update(p->ls,l,mid,dx); update(p->rs,mid+1,r,dx); } } int last; void query(node* p,int l,int r){ if( l==r ){ if( p->tag!=-1 && p->tag!=last ) ans[ p->tag ]++; last=p->tag; return; } pushdown(p); int mid = (p->l+p->r)>>1; if( r<mid ) query(p->ls,l,r); else if( l>mid ) query(p->rs,l,r); else{ query(p->ls,l,mid); query(p->rs,mid+1,r); } } int main(){ //ios::sync_with_stdio(false); int n; while( scanf("%d",&n)!=EOF ){ top=0; node* root = buildT(1,8000); for(int i=1;i<=n;i++){ int x1,x2,c; scanf("%d%d%d",&x1,&x2,&c); update(root,x1+1,x2,c); } memset(ans,0,sizeof(ans)); last=-1; query(root,1,8000); for(int i=0;i<=8000;i++){ if( ans[i] ) printf("%d %d ",i,ans[i]); } printf(" "); } return 0; }
【HDU 4027 Can you answer these queries?】
这题是要想到一个比较妙的性质然后就能做了,不过这题坑点挺多的,真是难度不够坑来凑。。
主要还是不知道要怎么更新一个区间,因为这个区间里的数都square root一下让区间和减小多少是不好说的。对于这种题就是要多想想,想到一个性质然后就能做了。关键点在于一个数square root最多7下就变成1了,而1以后就不影响了。所以更新的时候判断下当前区间是否都是1,如果都是1直接return(都是1的话就是区间和等于区间长度);不然的话不管它是不是终止结点(直到叶子结点为止)都继续更新下去。也就是说前几次都是O(n)代价更新,那为什么不会超时呢?因为当更新次数足够多以后,再更新的时候就会发现当前区间都是1,那么就自动变成logn更新了!O(n)线性更新的总代价应该是7*n级别的,完全可以过。
坑点在输入要是lld,我就是输入成了int,然后爆成负数,之后负数做sqrt还是返回负数,所以一直都是O(n)更新,因此竟然很多次都是tle而不是wa,给了我错误导向。。还有是两艘ship输入不保证大小关系,所以可能要swap一下
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define MAXN 200010 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) using namespace std; ll a[MAXN]; struct node{ int l,r; ll d,tag;//endurance & if all is 1 node *ls,*rs; }pool[4*MAXN]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l = l; p->r = r; p->tag = 0; if( l==r ){ p->d = a[l]; if( a[l]==1 ) p->tag=1; return p; } int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); p->d = p->ls->d + p->rs->d; p->tag = (p->ls->tag&&p->rs->tag); return p; } void update(node* p,int l,int r){ if( p->l==l && p->r==r && p->tag ){//all is one return; } if(p->l==p->r){//leaves p->d = long( sqrt(p->d) ); if( p->d==1 ) p->tag=1; return; } int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r); else if( l>mid ) update(p->rs,l,r); else{ update(p->ls,l,mid); update(p->rs,mid+1,r); } p->d = p->ls->d + p->rs->d; p->tag = p->ls->tag && p->rs->tag; return; } ll query(node* p,int l,int r){ if( p->l==l && p->r==r ) return p->d; int mid = (p->l+p->r)>>1; if( r<=mid ) return query(p->ls,l,r); else if( l>mid ) return query(p->rs,l,r); return query(p->ls,l,mid)+query(p->rs,mid+1,r); } int main(){ //ios::sync_with_stdio(false); int n,tc=0; while( scanf("%d",&n)!=EOF ){ printf("Case #%d: ",++tc); for(int i=1;i<=n;i++) scanf("%lld",a+i); int m; scanf("%d",&m); top=0; node* root = buildT(1,n); for(int i=1;i<=m;i++){ int t,x,y; scanf("%d%d%d",&t,&x,&y); if( x>y ) swap(x,y); if( t==0 ) update(root,x,y); else printf("%lld ",query(root,x,y)); } printf(" "); } return 0; }
【HDU 1540 Tunnel Warfare】
这题还是比较简单,画画图就想到区间修改单点求值了。
具体做法是将一个摧毁村庄拆成两个区间修改,分别是【当前被摧毁村庄到最近左被摧毁村庄这个区间】,减去当前村庄到最近右摧毁村庄间的村庄数量和【当前村庄和最近右村庄这个区间】,减去当前村庄到最近左村庄间的村庄数量。有一个坑点让我wa了很久,如果不是看到有博客写到这个我可能一辈子都想不到,以后思维要更全面才行
博客链接:https://www.xuebuyuan.com/1201209.html
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define MAXN 50010 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int l,r; int tag;//对于叶子结点是d值,对于其他结点就是tag node *ls,*rs; }pool[2*MAXN]; int top,n,m; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; if( l==r ){ p->tag=n; return p; } int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); p->tag=0; return p; } void update(node* p,int l,int r,int dx){ //cout<<p->l<<" "<<p->r<<" "<<l<<" "<<r<<" "<<dx<<endl; if(l>r) return; if( p->l==l && p->r==r ){ p->tag+=dx; return; } int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r,dx); else if( l>mid ) update(p->rs,l,r,dx); else{ update(p->ls,l,mid,dx); update(p->rs,mid+1,r,dx); } } void pushdown(node* p){ p->ls->tag+=p->tag; p->rs->tag+=p->tag; p->tag=0; } int query(node* p,int l){ if( p->l==l && p->r==l ) return p->tag; pushdown(p); int mid=(p->l+p->r)>>1; if( l<=mid ) return query(p->ls,l); return query(p->rs,l); } int del[MAXN]; int main(){ //freopen("1.in","r",stdin); //freopen("1.out","w",stdout); //ios::sync_with_stdio(false); while( scanf("%d%d",&n,&m)!=EOF ){ //区间修改,单点询问 //不需要维护区间值 map<int,int> mp; stack<int> s; memset(del,0,sizeof(del)); top=0; node* root = buildT(1,n); for(int i=1;i<=m;i++){ char a[2]; scanf("%s",a); if( a[0]=='D' ){ int x; scanf("%d",&x); if( del[x] ) { s.push(x); continue; } s.push(x); mp[x]=1; del[x]=1; map<int,int>::iterator it,it1,it2; it=mp.find(x); it2=(++it); it--; if( it!=mp.begin() ) { it1=(--it); it++; } if( it2!=mp.end() && it!=mp.begin() ){//左右两边都有其他被摧毁的village update(root,x+1,it2->first-1,-(x-it1->first));//先考虑右边的,减少的量是x到it1+1间城市的数量 update(root,it1->first+1,x-1,-(it2->first-x)); } else if( it2!=mp.end() && it==mp.begin() ){//右边有被摧毁的village,左边没有 update(root,x+1,it2->first-1,-x); update(root,1,x-1,-(it2->first-x)); } else if( it!=mp.begin() && it2==mp.end() ){//左边有被摧毁的village,右边没有 update(root,x+1,n,-(x-it1->first)); update(root,it1->first+1,x-1,-(n-x+1)); } else{ update(root,x+1,n,-x); update(root,1,x-1,-(n-x+1)); } } else if( a[0]=='Q' ){ int x; scanf("%d",&x); if( del[x] ) printf("0 "); else printf("%d ",query(root,x)); } else{ //数值直接取反 while( !s.empty() && del[ s.top() ]==0 ) s.pop(); if( s.empty() ) continue; int x=s.top(); s.pop(); del[x]=0; map<int,int>::iterator it,it1,it2; it=mp.find(x); it2=(++it); it--; if( it!=mp.begin() ) { it1=(--it); it++; } if( it2!=mp.end() && it!=mp.begin() ){//左右两边都有其他被摧毁的village update(root,x+1,it2->first-1,x-it1->first);//先考虑右边的,减少的量是x到it1+1间城市的数量 update(root,it1->first+1,x-1,it2->first-x); } else if( it2!=mp.end() && it==mp.begin() ){//右边有被摧毁的village,左边没有 update(root,x+1,it2->first-1,x); update(root,1,x-1,it2->first-x); } else if( it!=mp.begin() && it2==mp.end() ){//左边有被摧毁的village,右边没有 update(root,x+1,n,x-it1->first); update(root,it1->first+1,x-1,n-x+1); } else{ update(root,x+1,n,x); update(root,1,x-1,n-x+1); } mp.erase(x); } } } return 0; }
【HDU 3974 Assign the task】
由于在dfs树中,如果u是v的祖先,则有prev[u]<=prev[v]<=post[v]<=post[u],所以可以拿线段树维护这个dfs树上每个结点的值。
注意一下大小要开4*MAXN,因为打上时间戳后最大时间戳是2*N
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define MAXN 50010 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int l,r; int tag; node *ls,*rs; }pool[4*MAXN]; int vis[MAXN],cnt,Prev[MAXN],post[MAXN],top; vector<int> edge[MAXN]; void dfs(int u){ Prev[u] = ++cnt; for(int i=0;i<edge[u].size();i++) dfs( edge[u][i] ); post[u] = ++cnt; } node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->tag=-1;//一开始都没有干活 if( l==r ) return p; int mid=(l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); return p; } void pushdown(node* p){ if(p->tag==-1) return; p->ls->tag = p->rs->tag = p->tag; p->tag=-1; } void update(node* p,int l,int r,int dx){//这个区间里的人去干dx这个活 if( p->l==l && p->r==r ){ p->tag=dx; return; } pushdown(p); int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r,dx); else if( l>mid ) update(p->rs,l,r,dx); else{ update(p->ls,l,mid,dx); update(p->rs,mid+1,r,dx); } } int query(node* p,int l){ if( p->l==l && p->r==l ) return p->tag; pushdown(p); int mid = (p->l+p->r)>>1; if( l<=mid ) return query(p->ls,l); return query(p->rs,l); } int main(){ //freopen("1.in","r",stdin); //freopen("1.out","w",stdout); //ios::sync_with_stdio(false); int t,tc=0; scanf("%d",&t); while( t-- ){ for(int i=1;i<=50000;i++) edge[i].clear(); memset(vis,0,sizeof(vis)); cnt = top = 0; printf("Case #%d: ",++tc); int n; scanf("%d",&n); for(int i=1;i<n;i++){ int u,v; scanf("%d%d",&u,&v); vis[u]=1;//u不是root edge[v].push_back(u); } int rt; for(int i=1;i<=n;i++){ if( !vis[i] ) rt=i;//找到boss } dfs(rt); node* root = buildT(Prev[rt],post[rt]); int m; scanf("%d",&m); for(int i=1;i<=m;i++){ char a[2]; scanf("%s",a); if( a[0]=='C' ){ int x; scanf("%d",&x); printf("%d ",query(root,Prev[x])); } else{ int x,y; scanf("%d%d",&x,&y); update(root,Prev[x],post[x],y); } } } return 0; }
【HDU 4578 Transformation】
这一题算是线段树的一个分水槛了吧,在此之前的题都可以说是trivial,而这一题可以想清楚线段树的很多东西(比如update一路pushdown的话,可以保证如果一条链子上的两个结点都有tag,那下面结点的修改一定在上面结点的修改之前)。时限放宽到8秒可以说很良心了。
首先因为power只到3,所以我们完全可以把和,平方和,立方和都算出来。再看一共有3种区间修改,所以想到打3个tag分别表示区间加(tag1),区间乘(tag2),区间变成常数(tag3)。那么这里迎来了一个难题,怎么打tag?
我们不能说一个区间乘一个数那就在终止结点把tag2*=dx,加一个数就在终止结点把tag1+=dx,因为如果这么做的话我们不知道怎么处理出一个区间的和【因为比如tag1和tag2都有数的话,我们不知道是先加再乘还是先乘再加】
那怎么办?难点在于乘和加是两种不同的操作,那能不能找到一种操作使得其等效于加和乘效果的总和,然后修改的时候考虑加和乘对于我们定义的这种操作带来的贡献就行了?
因此想到记录区间的和为 data = (d*tag2)+tag1*len【即先乘再加,但其本质是人为的定义了一种修改,使得其免去了先加再乘还是先乘再加的问题】如果区间加dx,那就tag1+=dx;如果区间乘dx,那就tag1*=dx, tag2*=dx。拿笔推一推就能想通了。这样的话pushdown的时候该怎么做呢?当然还是先乘再加,(可以理解为不要把tag1和tag2看成两个操作,因为tag1指的是考虑tag2的乘对区间带来的影响后加的数值)所以这只是一种区间操作,这种区间操作是先乘tag1,再加tag2。那么这里还有第三种操作即区间变常数,这样的话我们要再修改一下之前定义的操作。那么我们考虑是先变常数,再乘加;还是先乘加再变常数。当然选择先变常数,修改的时候再记得把tag1=0,tag2=1;后变常数的话没法做,因为不管tag1,tag2怎么维护,最后都变成常数了。
那么解决了tag的问题,再想想区间修改怎么影响区间里要维护的和,平方和,立方和。拿区间加一个数对平方和的影响举例,问的就是
(x1+c)^2+(x2+c)^2+(x3+c)^2 ... (xn+c)^2 与 x1^2+x2^2+x3^2+...+xn^2之间有什么关系,简单推一下得到
d2' = d2 + len*c^2 + 2*c*d1 (d1是区间和,d2是原本的平方和,d2'是修改后的平方和)
注意一下这里的d1是x1+x2+x3+...+xn,所以是修改前的d1,因此实现的时候也得先修改d2,再修改d1。同理可以找到怎么维护区间和、立方和
还有是这题解决了我实现线段树的时候一直以来遇到的一个问题,在打tag的时候要不要立刻更新d值,还是在pushdown的时候再更新就很好了?
其实思考一下可以想到还是立刻更新d值更好,不然的话在pushup的时候要不断做getData,会带来很大的常数;与此相对的,如果立刻更新的话直接做p->d = p->ls->d + p->rs->d就好了。
对于这道题来说,如果一直用getData会tle;需要在打tag的时候直接更新d值才能过
最后再注意一下%mod,%跟乘是一个优先级,所以还蛮方便的
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define MAXN 100100 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) int mod = 10007; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int l,r; ll tag1,tag2,tag3;//tag1是加,tag2是乘, tag3是区间变 ll d1,d2,d3; node *ls,*rs; }pool[4*MAXN]; int top; node* buildT(int l,int r){ node* p = pool +(++top); p->l=l; p->r=r; p->tag1=0; p->tag2=1; p->tag3=0; p->d1 = p->d2 = p->d3 = 0; if( l==r ) return p; int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); return p; } void pushdown(node *p){ //因为更新tag3的时候,会将其他tag都消除,所以如果tag3有值而且其他tag也有值,那么说明是先更新的tag3 if( p->tag3 ){//tag3 先变 p->ls->tag3 = p->rs->tag3 = p->tag3; p->ls->tag1 = p->rs->tag1 = 0; p->ls->tag2 = p->rs->tag2 = 1; p->ls->d1 = (p->ls->r-p->ls->l+1)*p->tag3%mod; p->ls->d2 = p->ls->d1*p->tag3%mod; p->ls->d3 = p->ls->d2*p->tag3%mod; p->rs->d1 = (p->rs->r-p->rs->l+1)*p->tag3%mod; p->rs->d2 = p->rs->d1*p->tag3%mod; p->rs->d3 = p->rs->d2*p->tag3%mod; } if( p->tag2!=1 ){//tag2 再乘 p->ls->tag1*=p->tag2; p->rs->tag1*=p->tag2; p->ls->tag2*=p->tag2; p->rs->tag2*=p->tag2; p->ls->tag1%=mod; p->rs->tag1%=mod; p->ls->tag2%=mod; p->rs->tag2%=mod; p->ls->d1*=p->tag2; p->ls->d1%=mod; p->ls->d2*=p->tag2*p->tag2%mod; p->ls->d2%=mod; p->ls->d3*=p->tag2*p->tag2%mod*p->tag2%mod; p->ls->d3%=mod; p->rs->d1*=p->tag2; p->rs->d1%=mod; p->rs->d2*=p->tag2*p->tag2%mod; p->rs->d2%=mod; p->rs->d3*=p->tag2*p->tag2%mod*p->tag2%mod; p->rs->d3%=mod; } if( p->tag1 ){//tag1 再加 p->ls->tag1+=p->tag1; p->rs->tag1+=p->tag1; p->ls->tag1%=mod; p->rs->tag1%=mod; p->ls->d3+=((p->ls->r-p->ls->l+1)*p->tag1%mod*p->tag1%mod*p->tag1%mod+3*p->tag1*p->ls->d2%mod+3*p->tag1*p->tag1%mod*p->ls->d1%mod)%mod; p->ls->d3%=mod; p->ls->d2+=((p->ls->r-p->ls->l+1)*p->tag1%mod*p->tag1%mod+2*p->tag1*p->ls->d1%mod)%mod; p->ls->d2%=mod;//这里用到的d1是更新前的,所以先更新d2 p->ls->d1+=(p->ls->r-p->ls->l+1)*p->tag1; p->ls->d1%=mod; p->rs->d3+=((p->rs->r-p->rs->l+1)*p->tag1%mod*p->tag1%mod*p->tag1%mod+3*p->tag1*p->rs->d2%mod+3*p->tag1*p->tag1%mod*p->rs->d1%mod)%mod; p->rs->d3%=mod; p->rs->d2+=((p->rs->r-p->rs->l+1)*p->tag1%mod*p->tag1%mod+2*p->tag1*p->rs->d1%mod)%mod; p->rs->d2%=mod;//这里用到的d1是更新前的,所以先更新d2 p->rs->d1+=(p->rs->r-p->rs->l+1)*p->tag1; p->rs->d1%=mod; } p->tag1=0; p->tag2=1; p->tag3=0; } void update1(node* p,int l,int r,ll dx){//这个区间加dx if( p->l==l && p->r==r ){ p->tag1+=dx; p->tag1%=mod; p->d3+=((p->r-p->l+1)*dx%mod*dx%mod*dx%mod+3*dx*p->d2%mod+3*dx*dx%mod*p->d1%mod)%mod; p->d3%=mod; p->d2+=((p->r-p->l+1)*dx%mod*dx%mod+2*dx*p->d1%mod)%mod; p->d2%=mod;//这里用到的d1是更新前的,所以先更新d2 p->d1+=(p->r-p->l+1)*dx; p->d1%=mod; return; } pushdown(p);//如果tag上有标记的话,说明这个区间有被修改过,因此要先进行上次修改再进行这次修改 int mid = (p->l+p->r)>>1; if( r<=mid ) update1(p->ls,l,r,dx); else if(l>mid) update1(p->rs,l,r,dx); else{ update1(p->ls,l,mid,dx); update1(p->rs,mid+1,r,dx); } //做到这的一定都pushdown了,两个tag一定都是初始值 p->d1 = (p->ls->d1+p->rs->d1)%mod; p->d2 = (p->ls->d2+p->rs->d2)%mod; p->d3 = (p->ls->d3+p->rs->d3)%mod; } void update2(node *p,int l,int r,ll dx){//这个区间乘dx if(p->l==l && p->r==r){ p->tag1*=dx; p->tag1%=mod; p->tag2*=dx; p->tag2%=mod; p->d1*=dx; p->d1%=mod; p->d2*=dx*dx%mod; p->d2%=mod; p->d3*=dx*dx%mod*dx%mod; p->d3%=mod; return; } pushdown(p); int mid = (p->l+p->r)>>1; if( r<=mid ) update2(p->ls,l,r,dx); else if( l>mid ) update2(p->rs,l,r,dx); else{ update2(p->ls,l,mid,dx); update2(p->rs,mid+1,r,dx); } p->d1 = (p->ls->d1+p->rs->d1)%mod; p->d2 = (p->ls->d2+p->rs->d2)%mod; p->d3 = (p->ls->d3+p->rs->d3)%mod; } void update3(node *p,int l,int r,ll dx){//这个区间都变成dx if( p->l==l && p->r==r ){ p->tag1=0; p->tag2=1; p->tag3=dx; p->d1 = (p->r-p->l+1)*dx%mod; p->d2 = p->d1*dx%mod; p->d3 = p->d2*dx%mod; return; } int mid = (p->l+p->r)>>1; pushdown(p); if( r<=mid ) update3(p->ls,l,r,dx); else if( l>mid ) update3(p->rs,l,r,dx); else{ update3(p->ls,l,mid,dx); update3(p->rs,mid+1,r,dx); } p->d1 = (p->ls->d1+p->rs->d1)%mod; p->d2 = (p->ls->d2+p->rs->d2)%mod; p->d3 = (p->ls->d3+p->rs->d3)%mod; } int query(node* p,int l,int r,int type){ if( p->l==l && p->r==r ){ if( type==1 ) return p->d1; else if(type==2) return p->d2; return p->d3; } int mid = (p->l+p->r)>>1; pushdown(p); if( r<=mid ) return query(p->ls,l,r,type); else if(l>mid) return query(p->rs,l,r,type); return (query(p->ls,l,mid,type)+query(p->rs,mid+1,r,type))%mod; } //常数优化,在打上tag的时候就得更新d值,不然的话每次都要getdata很慢 int main(){ //freopen("1.in","r",stdin); //freopen("1.out","w",stdout); //ios::sync_with_stdio(false); int n,m; while( scanf("%d%d",&n,&m)!=EOF ){ if( n==0 && m==0 ) break; top=0; node* root = buildT(1,n); for(int i=1;i<=m;i++){ int op,x,y,c; scanf("%d%d%d%d",&op,&x,&y,&c); if( op==1 ) update1(root,x,y,c); else if( op==2 ) update2(root,x,y,c); else if( op==3 ) update3(root,x,y,c); else printf("%d ",query(root,x,y,c)); } } return 0; }
【HDU 4614 Vases and Flowers】
线段树上做二分
区间里面维护这个区间里【最左的空位】,【最右的空位】和【空位之和】
这一点的难点在于没有直接给你需要更新的区间,但如果能想到用二分来找的话就解决了!!
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define MAXN 50100 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) int mod = 10007; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int l,r; int sum,left,right,tag; node *ls,*rs; }pool[3*MAXN]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l = l; p->r=r; p->sum=r-l+1; p->left=l; p->right=r; p->tag=-1;//0代表插满花, 1代表下面的花都扔掉 if( l==r ) return p; int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); return p; } void pushdown(node* p){ if(p->tag==1){ p->ls->tag = p->rs->tag = 1; p->ls->left = p->ls->l; p->ls->right=p->ls->r; p->rs->left = p->rs->l; p->rs->right=p->rs->r; p->ls->sum = p->ls->r-p->ls->l+1; p->rs->sum = p->rs->r-p->rs->l+1; } else if( p->tag==0 ){ p->ls->tag = p->rs->tag = 0; p->ls->left = INF; p->ls->right=-INF; p->rs->left = INF; p->rs->right=-INF; p->ls->sum = 0; p->rs->sum = 0; } p->tag=-1; } void update(node* p,int l,int r,int type){ if( p->l==l && p->r==r ){//到了终止结点 p->tag=type; if( type==1 ){ p->left=l; p->right=r; p->sum=r-l+1; } else{ p->left=INF; p->right=-INF; p->sum=0; } //cout<<"??? "<<l<<" "<<r<<" "<<p->sum<<endl; return; } pushdown(p); int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r,type); else if( l>mid ) update(p->rs,l,r,type); else{ update(p->ls,l,mid,type); update(p->rs,mid+1,r,type); } //pushup p->sum = p->ls->sum + p->rs->sum; p->left = min(p->ls->left,p->rs->left); p->right = max(p->ls->right,p->rs->right); return; } int query(node* p,int l,int r){ //cout<<p->l<<" "<<p->r<<" "<<p->tag<<" "<<p->sum<<" "<<p->left<<" "<<p->right<<" "<<l<<" "<<r<<endl; if( p->l==l && p->r==r ){ // cout<<"!!! "<<l<<" "<<r<<" "<<p->sum<<endl; return p->sum; } pushdown(p); int mid = (p->l+p->r)>>1; if( r<=mid ) return query(p->ls,l,r); else if( l>mid ) return query(p->rs,l,r); return query(p->ls,l,mid)+query(p->rs,mid+1,r); } int query1(node* p,int l,int r){//询问这个区间第一个没有花的花瓶位置 if( p->l==l && p->r==r ) return p->left; pushdown(p); int mid = (p->l+p->r)>>1; if( r<=mid ) return query1(p->ls,l,r); else if( l>mid ) return query1(p->rs,l,r); return min( query1(p->ls,l,mid) , query1(p->rs,mid+1,r) ); } int query2(node* p,int l,int r){//询问这个区间最后一个没有花的花瓶位置 if( p->l==l && p->r==r ) return p->right; pushdown(p); int mid = (p->l+p->r)>>1; if( r<=mid ) return query2(p->ls,l,r); else if( l>mid ) return query2(p->rs,l,r); return max( query2(p->ls,l,mid) , query2(p->rs,mid+1,r) ); } int n,m; node* root; int bisearch(int A,int F){//找到从A更新到哪 //cout<<query(root,A,n)<<" "<<A<<" "<<n<<endl; if( query(root,A,n)==0 ) return 0; if( query(root,A,n)<F ) return n; int start=A,end=n,mid; while( end>start ){ mid = (start+end)/2; //cout<<"!!! "<<start<<" "<<mid<<" "<<end<<" "<<query(root,A,mid)<<endl; if( query(root,A,mid)>=F ) end=mid; else start=mid+1; //cout<<<<endl; } return end; } int main(){ //freopen("1.in","r",stdin); //freopen("1.out","w",stdout); //ios::sync_with_stdio(false); int t; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); top=0; root = buildT(1,n); for(int i=1;i<=m;i++){ int op,a,b; scanf("%d%d%d",&op,&a,&b); if( op==1 ){ a++; int y = bisearch(a,b); if( y==0 ) printf("Can not put any one. "); else{ //cout<<a<<" "<<y<<" "<<query1(root,a,y)<<" "<<query2(root,a,y)<<endl; printf("%d %d ",query1(root,a,y)-1,query2(root,a,y)-1); update(root,a,y,0); // cout<<a<<" "<<y<<endl; } } else if( op==2 ){ a++; b++; printf("%d ",b-a+1-query(root,a,b)); update(root,a,b,1); // cout<<a<<" "<<b<<endl; } // cout<<"query(root,1,10): "<<" "<<query(root,1,10)<<endl; } printf(" "); } return 0; }
【HDU 4553 约会安排】
其实就是区间合并,只不过要维护两个区间。
其次还有要开几个tag的问题,如果按照优先级的思路去考虑的话开三个tag很容易能实现。但我觉得开一个tag能做所以卡了很久,事实证明确实开一个tag可以做,不过有一个【坑点】。
那就是如果开一个tag的话,那你想的是pushdown和更新的时候是什么就直接push或直接更新tag就行了(因为覆盖的顺序就是先来后到的顺序)。但这么做的话要想到一个情况:一个终止结点先被清零,再被覆盖成屌丝。那就不能直接pushdown了,因为这个时候tag是1(代表屌丝)。你会想没关系啊,因为左子树右子树就应该都变成tag为1才对,而且之所以能更新也是在这个区间原本为空的基础上。【但我们错在没有考虑清零对女神区间的影响。】如果原来底下的区间是被女神覆盖的,那这个时候tag=1往下push,那会显示下面的区间同时被女神和屌丝覆盖,但实际上只被屌丝覆盖!
我原本以为屌丝区间无论怎么做都是不会影响女神区间的,考虑优先级的话也确实是如此,但如果取消掉优先级这个东西,就要多考虑一步清零操作对女神区间丢失的情况。
其次还有一个点是要能找到【最靠左的连续num个空区间位置】,即用线段树实现 “首次适应算法” ,那就是询问的时候看这连续num个空区间是不是在左子树,左子树一点加右子树一点,还是右子树。那就做完了
其实这题也是没有直接给你需要更新的区间,但通常都能找到一些性质来得到这个更新的区间。所以不用慌
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 100000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e5 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; //要回答出一个区间的最大连续空区间 //这个最大连续空间可能出现在哪,左,中,右 == 这样就能一次询问找到连续空区间的起点了 //操作有两种优先级不同的覆盖 struct node{ int l,r; int l1,r1,lian1;//优先级为1的覆盖的左边连续,右边连续,最大连续 int l2,r2,lian2; int tag;//0为清空,1为优先级为1的覆盖,2为优先级为2的覆盖 node *ls,*rs; }pool[2*MAXN]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->tag=-1; if( l==r ){ p->l1 = p->r1 = p->lian1 = 1; p->l2 = p->r2 = p->lian2 = 1; return p; } int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); p->l1 = p->ls->l1; if( p->ls->l1==p->ls->r-p->ls->l+1 ) p->l1+=p->rs->l1; p->r1 = p->rs->r1; if( p->rs->r1==p->rs->r-p->rs->l+1 ) p->r1+=p->ls->r1; p->lian1 = max( max( p->ls->lian1,p->rs->lian1 ), p->ls->r1+p->rs->l1); p->l2 = p->ls->l2; if( p->ls->l2==p->ls->r-p->ls->l+1 ) p->l2+=p->rs->l2; p->r2 = p->rs->r2; if( p->rs->r2==p->rs->r-p->rs->l+1 ) p->r2+=p->ls->r2; p->lian2 = max( max( p->ls->lian2,p->rs->lian2 ), p->ls->r2+p->rs->l2); return p; } void pushdown(node* p){ if( p->tag==-1 ) return; p->ls->tag = p->rs->tag = p->tag; if( p->tag==0 ){ p->ls->l1 = p->ls->r1 = p->ls->lian1 = p->ls->r-p->ls->l+1; p->rs->l1 = p->rs->r1 = p->rs->lian1 = p->rs->r-p->rs->l+1; p->ls->l2 = p->ls->r2 = p->ls->lian2 = p->ls->r-p->ls->l+1; p->rs->l2 = p->rs->r2 = p->rs->lian2 = p->rs->r-p->rs->l+1; } else if( p->tag==1 ){ p->ls->l1 = p->ls->r1 = p->ls->lian1 = 0; p->rs->l1 = p->rs->r1 = p->rs->lian1 = 0; p->ls->l2 = p->ls->r2 = p->ls->lian2 = p->ls->r-p->ls->l+1; p->rs->l2 = p->rs->r2 = p->rs->lian2 = p->rs->r-p->rs->l+1; } else{ p->ls->l1 = p->ls->r1 = p->ls->lian1 = 0; p->rs->l1 = p->rs->r1 = p->rs->lian1 = 0; p->ls->l2 = p->ls->r2 = p->ls->lian2 = 0; p->rs->l2 = p->rs->r2 = p->rs->lian2 = 0; } p->tag=-1; } void update(node* p,int l,int r,int dx){//将这个区间进行优先级为dx的覆盖 if( p->l==l && p->r==r ){ //if( p->l != p->r ) pushdown(p); p->tag=dx; if( dx==1 ){//屌丝 p->l1 = p->r1 = p->lian1 = 0;//对等级为2的不造成任何影响 p->l2 = p->r2 = p->lian2 = p->r-p->l+1; } else if(dx==2){//女神 p->l1 = p->r1 = p->lian1 = 0;//能对屌丝造成影响 p->l2 = p->r2 = p->lian2 = 0; } else{ p->l1 = p->r1 = p->lian1 = p->r-p->l+1; p->l2 = p->r2 = p->lian2 = p->r-p->l+1; } return; } pushdown(p); int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r,dx); else if( l>mid ) update(p->rs,l,r,dx); else{ update(p->ls,l,mid,dx); update(p->rs,mid+1,r,dx); } p->l1 = p->ls->l1; if( p->ls->l1==p->ls->r-p->ls->l+1 ) p->l1+=p->rs->l1; p->r1 = p->rs->r1; if( p->rs->r1==p->rs->r-p->rs->l+1 ) p->r1+=p->ls->r1; p->lian1 = max( max( p->ls->lian1,p->rs->lian1 ), p->ls->r1+p->rs->l1); p->l2 = p->ls->l2; if( p->ls->l2==p->ls->r-p->ls->l+1 ) p->l2+=p->rs->l2; p->r2 = p->rs->r2; if( p->rs->r2==p->rs->r-p->rs->l+1 ) p->r2+=p->ls->r2; p->lian2 = max( max( p->ls->lian2,p->rs->lian2 ), p->ls->r2+p->rs->l2); return; } int query(node* p,int num,int type){//寻找连续num个优先级为type的空位 //cout<<p->l<<" "<<p->r<<" "<<num<<" "<<type<<" "<<p->lian2<<" "<<p->l1<<" "<<p->l2<<endl; if( p->l==p->r ) return p->l; int mid = (p->l+p->r)>>1; pushdown(p); if( type==1 ){ if( p->ls->lian1>=num ) return query(p->ls,num,type); if( p->ls->r1+p->rs->l1>=num ) return mid-p->ls->r1+1; return query(p->rs,num,type); } else{ if( p->ls->lian2>=num ) return query(p->ls,num,type); if( p->ls->r2+p->rs->l2>=num ) return mid-p->ls->r2+1; return query(p->rs,num,type); } } int main(){ //ios::sync_with_stdio(false); int t,ca=0; scanf("%d",&t); while(t--){ int time,n; scanf("%d%d",&time,&n); top=0; node* root = buildT(1,time); printf("Case %d: ",++ca); for(int i=1;i<=n;i++){ char s[10]; scanf("%s",s); if( s[0]=='D' ){ int x; scanf("%d",&x); if( pool[1].lian1<x ) printf("fly with yourself "); else{ int l = query(root,x,1); update(root,l,l+x-1,1); printf("%d,let's fly ",l); } } else if( s[0]=='N' ){ int x; scanf("%d",&x); if( pool[1].lian1<x ){ if( pool[1].lian2<x ) printf("wait for me "); else{ int l = query(root,x,2); update(root,l,l+x-1,2); printf("%d,don't put my gezi ",l); } } else{ int l = query(root,x,1); update(root,l,l+x-1,2); printf("%d,don't put my gezi ",l); } } else{ int x,y; scanf("%d%d",&x,&y); update(root,x,y,0); printf("I am the hope of chinese chengxuyuan!! "); } } } return 0; } //freopen("1.in","r",stdin); //freopen("1.out","w",stdout);
【POJ 1177 Picture】
求合并后矩形的周长。对于y轴建线段树,然后扫描纵边,将纵边覆盖在线段树上。每次周长的增量是 |【当前y轴被覆盖的长度】- 【加入这条扫描线前y轴被覆盖的长度】|,很直觉。
这里想了很久为什么可以不写pushdown,本质还是没想清楚区间里维护的tag,sum代表什么意思。下一道题里有说
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e4 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int l,r; int sum,tag;//sum是被覆盖长度,tag是被完全覆盖的次数 node *ls,*rs; }pool[4*MAXN]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->sum = p->tag = 0; if( l==r ) return p; int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); return p; } void update(node* p,int l,int r,int dx){//对l,r这个区间进行更新 //cout<<p->l<<" "<<p->r<<" "<<l<<" "<<r<<" "<<dx<<" "<<p->ls->l<<" "<<p->ls->r<<" "<<p->rs->l<<" "<<p->rs->r<<endl; if( p->l==l && p->r==r ){ p->tag+=dx; if( p->tag ) p->sum=r-l+1; else{ if( l==r ) p->sum=0; else p->sum=p->ls->sum+p->rs->sum; } return; } int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r,dx); else if(l>mid) { //cout<<"!! "<<p->rs->l<<" "<<p->rs->r<<endl; update(p->rs,l,r,dx); } else{ update(p->ls,l,mid,dx); update(p->rs,mid+1,r,dx); } if( p->tag ) p->sum=p->r-p->l+1; else p->sum = p->ls->sum + p->rs->sum; } struct Scanline{ int x,y1,y2,type; Scanline(int x1=0,int y11=0,int y22=0,int t=0): x(x1),y1(y11),y2(y22),type(t) {} bool operator < (const Scanline &p)const{ return x<p.x; } }lines1[3*MAXN],lines2[3*MAXN];//line1是竖直扫描线,line2是水平扫描线 int main(){ //ios::sync_with_stdio(false); int n; while( scanf("%d",&n)!=EOF ){ int x1,y1,x2,y2,ans=0; int id=0; for(int i=1;i<=n;i++){ scanf("%d%d%d%d",&x1,&y1,&x2,&y2); x1+=MAXN; y1+=MAXN; x2+=MAXN; y2+=MAXN; lines1[++id]=Scanline(x1,y1,y2,1); lines2[id]=Scanline(y1,x1,x2,1); lines1[++id]=Scanline(x2,y1,y2,-1); lines2[id]=Scanline(y2,x1,x2,-1); } sort(lines1+1,lines1+1+id); //cout<<lines1[1].x<<" "<<lines1[2].x<<endl; top=0; node* root = buildT(1,2*MAXN); //cout<<"!!!"<<endl; int last=0; for(int i=1;i<=id;i++){//竖直扫描线 int l=lines1[i].y1; int r=lines1[i].y2-1; //cout<<lines1[i].x<<" "<<l<<" "<<r<<" "<<lines1[i].type<<endl; update(root,l,r,lines1[i].type); ans+=abs(root->sum-last); last=root->sum; } //cout<<"!!!"<<endl; sort(lines2+1,lines2+1+id); top=0; root=buildT(1,2*MAXN); last=0; for(int i=1;i<=id;i++){//水平扫描线 int l=lines2[i].y1; int r=lines2[i].y2-1; update(root,l,r,lines2[i].type); ans+=abs(root->sum-last); last=root->sum; } printf("%d ",ans); } return 0; } //freopen("1.in","r",stdin); //freopen("1.out","w",stdout);
【HDU 1255 覆盖面积】
被矩形覆盖两次及以上的面积。对于y轴建线段树,然后扫描纵边,将纵边覆盖在线段树上。每次面积的增量是【当前整个y轴被覆盖两次及以上的线段长度】乘【当前扫描线到下一条扫描线间的距离】。
维护tag表示被完全覆盖几次,sum1被重复覆盖一次及以上的长度,sum2被重复覆盖两次及以上的长度。
那如果tag=1的话,p->sum2 = p->ls->sum1 + p->rs->sum1就行了,因为sum1和sum2代表的含义是【不考虑祖先结点被覆盖(因为不pushdown),只考虑我及我后代被覆盖的情况下】被覆盖至少一次/两次的长度。这么想的话就很好理解了。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e4 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct Scanline{ double x; double y1,y2; int flag; Scanline(double x1=0,double y11=0,double y22=0,int f1=0): x(x1),y1(y11),y2(y22),flag(f1) {} bool operator < (const Scanline &p)const{ return x<p.x; } }lines[4*MAXN]; double heng[4*MAXN]; map<int,double> m1; map<double,int> m2; struct node{ int l,r; double sum2,sum1;//sum2被重复覆盖两次及以上的面积,被重复覆盖一次及以上的面积 int tag;//tag被重复覆盖几次,sum被两次完全覆盖的数量 node *ls,*rs; }pool[4*MAXN]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->sum2=0; p->tag=0; p->sum1=0; if( l==r ) return p; int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); return p; } void update(node* p,int l,int r,int dx){ //cout<<"!!! "<<p->l<<" "<<p->r<<" "<<l<<" "<<r<<" "<<dx<<endl; if( p->l==l && p->r==r ){ p->tag+=dx; if( p->tag>=2 ) { p->sum2 = m1[r+1]-m1[l]; p->sum1 = p->sum2; } else if( l==r ){ p->sum2=0; if( p->tag ) p->sum1=m1[r+1]-m1[l]; else p->sum1=0; } else{ if( p->tag==1 ){ // cout<<"bingo"<<endl; p->sum2 = p->ls->sum1+p->rs->sum1; //cout<<m1[r+1]<<" "<<m1[l]<<endl; p->sum1 = m1[r+1]-m1[l]; } else{ p->sum2 = p->ls->sum2+p->rs->sum2; p->sum1 = p->ls->sum1+p->rs->sum1; } } // cout<<"zhongzhi"<<" "<<p->l<<" "<<p->r<<" "<<p->sum1<<" "<<p->sum2<<endl; return; } int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r,dx); else if( l>mid ) update(p->rs,l,r,dx); else{ update(p->ls,l,mid,dx); update(p->rs,mid+1,r,dx); } if( p->tag>=2 ) { //cout<<"??"<<endl; //cout<<r<<" "<<l<<" "<<m1[r+1]<<" "<<m1[l]<<endl; p->sum2 = p->sum1 = m1[p->r+1]-m1[p->l]; } else if( p->tag==1 ){ p->sum2 = p->ls->sum1+p->rs->sum1; p->sum1 = m1[p->r+1]-m1[p->l]; } else{ p->sum2 = p->ls->sum2+p->rs->sum2; p->sum1 = p->ls->sum1+p->rs->sum1; } // cout<<"boom "<<p->l<<" "<<p->r<<" "<<p->sum2<<endl; } int main(){ //ios::sync_with_stdio(false); int t; scanf("%d",&t); while(t--){ m1.clear(); m2.clear(); int n,id=0; scanf("%d",&n); for(int i=1;i<=n;i++){ double x1,y1,x2,y2; scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); lines[++id]=Scanline(x1,y1,y2,1); heng[id]=y1; lines[++id]=Scanline(x2,y1,y2,-1); heng[id]=y2; } sort(heng+1,heng+1+id);//还要把所有横边离散化 int num = unique(heng+1,heng+1+id)-(heng+1); for(int i=1;i<=num;i++){ m1[i] = heng[i]; m2[ heng[i] ] = i; //cout<<heng[i]<<" "<<i<<endl; } //cout<<".. "<<m2[1]<<endl; top=0; node* root = buildT(1,num);//对y轴建线段树 double ans=0; sort(lines+1,lines+1+id); for(int i=1;i<=id-1;i++){ //面积是目前双次以上覆盖线段长度 * 到下一个线段的距离 //cout<<lines[i].x<<" "<<lines[i].y1<<" "<<lines[i].y2<<" "<<m2[ lines[i].y1 ]<<" "<<m2[ lines[i].y2 ]<<endl; update(root,m2[ lines[i].y1 ],m2[ lines[i].y2 ]-1,lines[i].flag); // cout<<root->sum2<<" "<<lines[i+1].x-lines[i].x<<endl; ans+=root->sum2*( lines[i+1].x-lines[i].x ); // cout<<"!!! "<<ans<<endl; } printf("%.2lf ",ans); //cout<<fixed<<setprecision(3)<<ans; } return 0; } //freopen("1.in","r",stdin); //freopen("1.out","w",stdout);
【HDU 1542 Atlantis】
被矩形覆盖一次及以上的面积。比上一题还简单不说了,经典题
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e4 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct Scanline{ double x; double y1,y2; int flag; Scanline(double x1=0,double y11=0,double y22=0,int f1=0): x(x1),y1(y11),y2(y22),flag(f1) {} bool operator < (const Scanline &p)const{ return x<p.x; } }lines[4*MAXN]; double heng[4*MAXN]; map<int,double> m1; map<double,int> m2; struct node{ int l,r; double sum; int tag;//tag被重复覆盖几次,sum被两次完全覆盖的数量 node *ls,*rs; }pool[4*MAXN]; int top; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->tag=0; p->sum=0; if( l==r ) return p; int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); return p; } void update(node* p,int l,int r,int dx){ //cout<<"!!! "<<p->l<<" "<<p->r<<" "<<l<<" "<<r<<endl; if( p->l==l && p->r==r ){ p->tag+=dx; if( p->tag>=1 ) p->sum = m1[r+1]-m1[l]; else if( l==r ) p->sum=0; else p->sum = p->ls->sum+p->rs->sum; // cout<<"zhongzhi"<<" "<<l<<" "<<p->sum<<endl; return; } int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r,dx); else if( l>mid ) update(p->rs,l,r,dx); else{ update(p->ls,l,mid,dx); update(p->rs,mid+1,r,dx); } if( p->tag ) { p->sum = m1[p->r+1]-m1[p->l]; } else p->sum = p->ls->sum+p->rs->sum; // cout<<"pushup "<<p->l<<" "<<p->r<<" "<<p->sum<<endl; } int main(){ //ios::sync_with_stdio(false); int ca=0; while(1){ m1.clear(); m2.clear(); int n,id=0; scanf("%d",&n); if( n==0 ) break; for(int i=1;i<=n;i++){ double x1,y1,x2,y2; scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); lines[++id]=Scanline(x1,y1,y2,1); heng[id]=y1; lines[++id]=Scanline(x2,y1,y2,-1); heng[id]=y2; } sort(heng+1,heng+1+id);//还要把所有横边离散化 int num = unique(heng+1,heng+1+id)-(heng+1); for(int i=1;i<=num;i++){ m1[i] = heng[i]; m2[ heng[i] ] = i; } top=0; node* root = buildT(1,num);//对y轴建线段树 double ans=0; sort(lines+1,lines+1+id); for(int i=1;i<=id-1;i++){ //面积是目前双次以上覆盖线段长度 * 到下一个线段的距离 // cout<<lines[i].x<<" "<<lines[i].y1<<" "<<lines[i].y2<<" "<<lines[i].flag<<endl; update(root,m2[ lines[i].y1 ],m2[ lines[i].y2 ]-1,lines[i].flag); ans+=root->sum*( lines[i+1].x-lines[i].x ); // cout<<root->sum<<" "<<lines[i+1].x-lines[i].x<<endl; } printf("Test case #%d ",++ca); printf("Total explored area: %.2lf ",ans); printf(" "); } return 0; } //freopen("1.in","r",stdin); //freopen("1.out","w",stdout);
【HDU 3642 Get The Treasury】
被立方体覆盖三次及以上的体积。乍一看没有思路,但实际上可以枚举z轴,然后每次只考虑包含了当前z轴的立方体。这样就去掉了一维,所以就变成了被矩形覆盖三次及以上的面积,就好做了。
好题!
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<string> #include<stack> #include<fstream> #include<map> #include<iomanip> #include<algorithm> #include<vector> #define INF 2e9 #define maxnode 200000 #define ll long long #define lowbit(x) (x&(-x)) const int mod = 10007; const int MAXN = 1e3 + 10; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; using namespace std; struct node{ int l,r; int sum1,sum2,sum3,tag; node *ls,*rs; }pool[4*MAXN]; struct Scanline{ int x; int y1,y2; int flag; Scanline(int x1=0,int y11=0,int y22=0,int f1=0): x(x1),y1(y11),y2(y22),flag(f1) {} bool operator < (const Scanline &p)const{ return x<p.x; } }lines[4*MAXN]; struct point{ int x,y,z; point(int x1,int y1,int z1): x(x1),y(y1),z(z1) {} }; struct cube{ point a,b; cube(point a1=point(0,0,0),point b1=point(0,0,0)): a(a1),b(b1) {} }cubes[4*MAXN]; //覆盖三次及以上的最小体积 map<int,int> m1,m2; int heng[4*MAXN],top; node* buildT(int l,int r){ node* p = pool + (++top); p->l=l; p->r=r; p->sum1 = p->sum2 = p->sum3 = p->tag=0; if( l==r ) return p; int mid = (l+r)>>1; p->ls = buildT(l,mid); p->rs = buildT(mid+1,r); return p; } void update(node* p,int l,int r,int dx){ //cout<<p->l<<" "<<p->r<<" "<<l<<" "<<r<<" "<<dx<<endl; if( p->l==l && p->r==r ){ p->tag+=dx; if( p->tag>=3 ){ p->sum3 = p->sum2 = p->sum1 = m1[r+1]-m1[l]; } else if( l==r ){ p->sum3=0; if( p->tag==2 ) p->sum2 = p->sum1 = m1[r+1]-m1[l]; else if( p->tag==1 ) p->sum2=0,p->sum1=m1[r+1]-m1[l]; else p->sum2 = p->sum1 = 0; } else if( p->tag==2 ){ p->sum3 = p->ls->sum1 + p->rs->sum1; p->sum2 = p->sum1 = m1[r+1]-m1[l]; } else if( p->tag==1 ){ p->sum3 = p->ls->sum2+p->rs->sum2; p->sum2 = p->ls->sum1+p->rs->sum1; p->sum1 = m1[r+1]-m1[l]; } else{ p->sum3 = p->ls->sum3+p->rs->sum3; p->sum2 = p->ls->sum2+p->rs->sum2; p->sum1 = p->ls->sum1+p->rs->sum1; } return; } int mid = (p->l+p->r)>>1; if( r<=mid ) update(p->ls,l,r,dx); else if( l>mid ) update(p->rs,l,r,dx); else{ update(p->ls,l,mid,dx); update(p->rs,mid+1,r,dx); } if( p->tag>=3 ) p->sum3 = p->sum2 = p->sum1 = m1[p->r+1]-m1[p->l]; else if( p->tag==2 ){ p->sum3 = p->ls->sum1 + p->rs->sum1; p->sum2 = p->sum1 = m1[p->r+1]-m1[p->l]; } else if( p->tag==1 ){ p->sum3 = p->ls->sum2+p->rs->sum2; p->sum2 = p->ls->sum1+p->rs->sum1; p->sum1 = m1[p->r+1]-m1[p->l]; } else{ p->sum3 = p->ls->sum3+p->rs->sum3; p->sum2 = p->ls->sum2+p->rs->sum2; p->sum1 = p->ls->sum1+p->rs->sum1; } } int main(){ //ios::sync_with_stdio(false); int t,ca=0; scanf("%d",&t); while(t--){ m1.clear(); m2.clear(); int n; scanf("%d",&n); int num=0; for(int i=1;i<=n;i++){ int x1,y1,z1,x2,y2,z2; scanf("%d%d%d%d%d%d",&x1,&y1,&z1,&x2,&y2,&z2); heng[++num] = y1; heng[++num] = y2; cubes[i] = cube( point(x1,y1,z1),point(x2,y2,z2) ); } sort(heng+1,heng+1+num); num = unique(heng+1,heng+1+num)-(heng+1); for(int i=1;i<=num;i++){//对于y点要离散化 m1[i] = heng[i]; m2[ heng[i] ] = i; } long long ans=0; for(int z=-500;z<=500;z++){//枚举z轴,这样就转化成了矩形求交 int id=0; for(int i=1;i<=n;i++){//遍历已有的全部cube if( cubes[i].a.z<=z && cubes[i].b.z>z ){ lines[++id] = Scanline(cubes[i].a.x,cubes[i].a.y,cubes[i].b.y,1); lines[++id] = Scanline(cubes[i].b.x,cubes[i].a.y,cubes[i].b.y,-1); } } sort(lines+1,lines+1+id); top=0; node* root = buildT(1,num); for(int i=1;i<=id-1;i++){ update(root,m2[ lines[i].y1 ],m2[lines[i].y2]-1,lines[i].flag); ans+=(long long)root->sum3*( lines[i+1].x-lines[i].x ); } } printf("Case %d: %lld ",++ca,ans); } return 0; } //freopen("1.in","r",stdin); //freopen("1.out","w",stdout);
最后总结,线段树的本质是结合律。比如一个区间更新我们可以把它看成多个对终止结点的更新,一个区间询问也是由多个对终止结点的询问拼起来的。剩下的是靠题目里的一些性质去搞一些技巧性的东西,或玩tag。比如维护立方和,线段树上做二分,O(n)暴力更新