1、 线段树是二叉树,且必定是平衡二叉树,但不一定是完全二叉树。
2、 对于区间[ L , R ],令mid=(L+R)/2,则其左子树为[L,mid],右子树为[mid+1,R],当L==R时,该区间为线段树的叶子,无需继续往下划分。
3、 线段树虽然不是完全二叉树,但是可以用完全二叉树的方式去构造并存储它,只是最后一层可能存在某些叶子与叶子之间出现“空叶子”,这个无需理会,同样给空叶子按顺序编号,在遍历线段树时当判断到a==b时就认为到了叶子,“空叶子”永远也不会遍历到。
4、 之所以要用完全二叉树的方式去存储线段树,是为了提高在插入线段和搜索时的效率。用p2,p2+1的索引方式检索p的左右子树要比指针快得多。
5、线段树的精髓是,能不往下搜索,就不要往下搜索,尽可能利用子树的根的信息去获取整棵子树的信息。如果在插入线段或检索特征值时,每次都非要搜索到叶子,还不如直接建一棵普通树更来得方便
6、对一维线段树来说,每次更新以及查询的时间复杂度为 O ( l o g n ) O(logn) O(logn)。常用的解决RMQ问题有ST算法,二者预处理时间都是 O ( n l o g n ) O(nlogn) O(nlogn)
总结:
1、线段树是平衡二叉树,并以完全二叉树的方式构造,但并不一定是完全二叉树。有些空的叶节点永远都遍历不到
2、尽量不要遍历要叶节点,即L==R,Update和Query的时候,尽量更新到根节点,然后配合lazy数组来使用。但有时候很难只更新到区间,用lazy很难表示区间和的累积。此时只能做单点更新
3、用线段树解题时,先想明白,用线段树的叶节点来维护什么,用根节点来维护什么
4、单点更新表示L==R,区间更新利用(l<=L&&R<=r)+ lazy数组维护。如果要走到叶节点就L==R,如果只走到某个区间就(l<=L&&R<=r)即可。
5、线段树给的区间大小,确定叶节点所维护的大小,4倍即可
(1)Build函数
(2)Update函数
if(l<=mid)
ans+=Query(ls,l,r,c);
if(r>mid)
ans+=Query(rs,l,r,c);
return ans;
1、敌兵布阵 HDU - 1166
类型:单点更新,区间查询
题意:三种操作Query l r 输出区间l–>r的和。add x y,对x点加y,以及sub x y对x点减y
思路:用叶节点维护每一个营地的人数,用根节点维护区间的总人数
更新到叶节点,查询到所包含区间
const int maxn=5e4+10,INF=0x3f3f3f3f;
const int mod=1e9+7;
int ST[maxn<<2];
void Push_up(int rt)
{
ST[rt]=ST[ls]+ST[rs];
}
void Build(int rt,int l,int r)
{
if(l==r)
{
scanf("%d",&ST[rt]);
return;
}
int mid=(l+r)>>1;
Build(ls,l,mid);
Build(rs,mid+1,r);
Push_up(rt);
}
void Update(int rt,int p,int l,int r,int c)
{
if(l==r)
{
ST[rt]+=c;
return;
}
int mid=(l+r)>>1;
if(p<=mid)
Update(ls,p,l,mid,c);
if(p>mid)
Update(rs,p,mid+1,r,c);
Push_up(rt);
}
int Query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt];
int mid=(L+R)>>1;
int ans=0;
if(l<=mid)
ans+=Query(ls,l,r,L,mid);
if(r>mid)
ans+=Query(rs,l,r,mid+1,R);
return ans;
}
int main()
{
int T;
scanf("%d",&T);
int Cas=0;
while(T--)
{
int n;
scanf("%d",&n);
Build(1,1,n);
char op[10];
printf("Case %d:\n",++Cas);
while(~scanf("%s",&op))
{
if(op[0]=='Q')
{
int l,r;
scanf("%d %d",&l,&r);
printf("%d\n",Query(1,l,r,1,n));
}
else if(op[0]=='A')
{
int p,c;
scanf("%d %d",&p,&c);
Update(1,p,1,n,c);
}
else if(op[0]=='E')
break;
else if(op[0]=='S')
{
int p,c;
scanf("%d %d",&p,&c);
Update(1,p,1,n,-c);
}
}
}
return 0;
}
2、Can you answer these queries? HDU - 4027
类型:区间更新(无法对区间直接更新,只能做单点更新),区间查询
题意:两种操作
0 a b 代表将区间[a,b]内的数值,变为自己的平方根
1 a b 代表查询区间[a,b]内总值
思路:叶节点维护初始战斗力,根节点维护区间和。对区间中的每个点做开方的更新操作,虽然是区间更新,但是很难直接累加到lazy数组上。因此还是要做单点跟新
单点更新时,只需要明白一件事,就是一个最大的数,最多开6、7次平方,就会变成1。因此当一个区间的的数值ST[ rt ] == R-L+1就说明它的叶子节点都已经是1了,剪枝即可
const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,M;
ll ST[maxn<<2];int T,X,Y;
void Push_up(int rt)
{
ST[rt]=ST[ls]+ST[rs];
}
void Build(int rt,int L,int R)
{
if(L==R)
{
scanf("%lld",&ST[rt]);
return;
}
int mid=(L+R)>>1;
Build(ls,L,mid);
Build(rs,mid+1,R);
Push_up(rt);
}
void Update(int rt,int l,int r,int L,int R)
{
if((R-L+1)==ST[rt])
return;
if(L==R)
{
ST[rt]=sqrt(ST[rt]);
return;
}
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid);
if(r>mid)
Update(rs,l,r,mid+1,R);
Push_up(rt);
}
ll Query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
{
return ST[rt];
}
int mid=(L+R)>>1;
ll ans=0;
if(l<=mid)
ans+=Query(ls,l,r,L,mid);
if(r>mid)
ans+=Query(rs,l,r,mid+1,R);
return ans;
}
int main()
{
int Cas=0;
while(~scanf("%d",&N))
{
printf("Case #%d:\n",++Cas);
Build(1,1,N);
scanf("%d",&M);
while(M--)
{
scanf("%d %d %d",&T,&X,&Y);
if(X>Y)
swap(X,Y);
if(T==0)
{
Update(1,X,Y,1,N);
}
else if(T==1)
{
printf("%lld\n",Query(1,X,Y,1,N));
}
}
printf("\n");
}
return 0;
}
B - I Hate It HDU - 1754
类型:单点更新,区间查询
题意:两种操作Q l r:输出区间l–>r的最高成绩。U x y:将学号x的学生的成绩更新为y
思路:叶节点维护每一个学生的成绩,根节点维护区间的最高成绩
更新到叶节点(L==R),查询到所包含区间(l<=L&&R<=r)
const int maxn=2e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,M,x,y;int ST[maxn<<2];
char op[10];
void Push_up(int rt)
{
ST[rt]=max(ST[ls],ST[rs]);
}
void Build(int rt,int L,int R)
{
if(L==R)
{
scanf("%d",&ST[rt]);
return;
}
int mid=(L+R)>>1;
Build(ls,L,mid);
Build(rs,mid+1,R);
Push_up(rt);
}
void Update(int rt,int p,int L,int R,int c)
{
if(L==R)
{
ST[rt]=c;
return;
}
int mid=(L+R)>>1;
if(p<=mid)
Update(ls,p,L,mid,c);
if(p>mid)
Update(rs,p,mid+1,R,c);
Push_up(rt);
}
int Query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt];
int ans=-INF;
int mid=(L+R)>>1;
if(l<=mid)
ans=max(ans,Query(ls,l,r,L,mid));
if(r>mid)
ans=max(ans,Query(rs,l,r,mid+1,R));
return ans;
}
int main()
{
while(~scanf("%d %d",&N,&M))
{
Build(1,1,N);
while(M--)
{
scanf("%s %d %d",&op,&x,&y);
if(op[0]=='Q')
{
printf("%d\n",Query(1,x,y,1,N));
}
else if(op[0]=='U')
{
Update(1,x,1,N,y);
}
}
}
return 0;
}
3、C - A Simple Problem with Integers POJ - 3468
类型:区间更新,区间查询
题意:两种操作
“C a b c” means adding c to each of Aa, Aa+1, … , Ab.
“Q a b” means querying the sum of Aa, Aa+1, … , Ab.
思路:叶节点维护一个数值,根节点维护区间和
更新到所包含区间(l<=L&&R<=r),查询到所包含区间(l<=L&&R<=r)
所有改变累积到lazy数组上,向下推时做更新
const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,Q;char op[10];
ll ST[maxn<<2],lazy[maxn<<2];
void Push_up(int rt)
{
ST[rt]=ST[ls]+ST[rs];
}
void Push_down(int rt,int L,int R)
{
if(lazy[rt])
{
lazy[ls]+=lazy[rt];
lazy[rs]+=lazy[rt];
int mid=(L+R)>>1;
ST[ls]+=(mid-L+1)*lazy[rt];
ST[rs]+=(R-mid)*lazy[rt];
lazy[rt]=0;
}
}
void Build(int rt,int L,int R)
{
if(L==R)
{
scanf("%lld",&ST[rt]);
return;
}
ll mid=(L+R)>>1;
Build(ls,L,mid);
Build(rs,mid+1,R);
Push_up(rt);
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
lazy[rt]+=c;
ST[rt]+=(R-L+1)*c;
return;
}
Push_down(rt,L,R);
ll mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
Push_up(rt);
}
ll Query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt];
Push_down(rt,L,R);
ll ans=0;
ll mid=(L+R)>>1;
if(l<=mid)
ans+=Query(ls,l,r,L,mid);
if(r>mid)
ans+=Query(rs,l,r,mid+1,R);
return ans;
}
int main()
{
while(~scanf("%d %d",&N,&Q))
{
Build(1,1,N);
while(Q--)
{
scanf("%s",op);
if(op[0]=='Q')
{
int l,r;
scanf("%d %d",&l,&r);
printf("%lld\n",Query(1,l,r,1,N));
}
else if(op[0]=='C')
{
int l,r,c;
scanf("%d %d %d",&l,&r,&c);
Update(1,l,r,1,N,c);
}
}
}
return 0;
}
4、D - Mayor’s posters POJ - 2528
const int maxn=1e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int ST[maxn<<3],visit[maxn];int ans,X[maxn<<1];
struct Poster
{
int l,r;
}posters[maxn];
void Push_down(int rt){
if(ST[rt])
{
ST[ls]=ST[rt];
ST[rs]=ST[rt];
ST[rt]=0;
}
}
void Update(int rt,int l,int r,int L,int R,int c){
if(l<=L&&R<=r)
{
ST[rt]=c;
return;
}
Push_down(rt);
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
}
void Query(int rt,int L,int R){
if(!visit[ST[rt]]&&ST[rt]!=0)
{
ans++;
visit[ST[rt]]=1;
return;
}
if(visit[ST[rt]]|L==R)
return;
int mid=(L+R)>>1;
Query(ls,L,mid);
Query(rs,mid+1,R);
}
int main(){
int T,n;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
rep(i,1,n)
{
scanf("%d %d",&posters[i].l,&posters[i].r);
X[i*2-1]=--posters[i].l;
X[i*2]=posters[i].r;
}
sort(X+1,X+1+2*n);
int k=unique(X+1,X+1+2*n)-X-1;
mes(ST,0);
mes(visit,0);
for(int i=1;i<=n;++i)
{
int l=lower_bound(X+1,X+1+k,posters[i].l)-X;
int r=lower_bound(X+1,X+1+k,posters[i].r)-X-1;
if(l<=r)
Update(1,l,r,1,k-1,i);
}
ans=0;
Query(1,1,k-1);
printf("%d\n",ans);
}
return 0;
}
4、Mayor’s posters POJ - 2528
类型:区间更新、染色、离散化
题意:报纸的高度确定,给出宽度的区间,将报纸按顺序覆盖,问最后能看到多少张报纸?
思路:离散化X轴的坐标,每个叶节点维护一个离散化之后的区间([ x[ i ] , x[ i ] + 1 ])上的颜色,根节点维护整个区间的颜色,当整个区间的颜色不一致时,区间不设颜色,即ST[ rt ]=-1
最后统计答案的时候,设置一个visit数组,表示该颜色是否访问。遍历整棵线段树,如果根节点是有颜色的,就不用访问下去了,如果是没有颜色的,就一直访问到叶节点为止
const int maxn=1e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int ST[maxn<<3],visit[maxn];int ans,X[maxn<<1];
struct Poster
{
int l,r;
}posters[maxn];
void Push_down(int rt)
{
if(ST[rt])
{
ST[ls]=ST[rt];
ST[rs]=ST[rt];
ST[rt]=0;
}
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
ST[rt]=c;
return;
}
Push_down(rt);
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
}
void Query(int rt,int L,int R)
{
if(!visit[ST[rt]]&&ST[rt]!=0)
{
ans++;
visit[ST[rt]]=1;
return;
}
if(visit[ST[rt]]|L==R)
return;
int mid=(L+R)>>1;
Query(ls,L,mid);
Query(rs,mid+1,R);
}
int main()
{
int T,n;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
rep(i,1,n)
{
scanf("%d %d",&posters[i].l,&posters[i].r);
X[i*2-1]=--posters[i].l;
X[i*2]=posters[i].r;
}
sort(X+1,X+1+2*n);
int k=unique(X+1,X+1+2*n)-X-1;
mes(ST,0);
mes(visit,0);
for(int i=1;i<=n;++i)
{
int l=lower_bound(X+1,X+1+k,posters[i].l)-X;
int r=lower_bound(X+1,X+1+k,posters[i].r)-X-1;
if(l<=r)
Update(1,l,r,1,k-1,i);
}
ans=0;
Query(1,1,k-1);
printf("%d\n",ans);
}
return 0;
}
F - Count the Colors ZOJ - 1610
类型:区间更新、染色、离散化(这题数据比较小,可以不离散化)
题意:给指定区间染成指定颜色,按颜色的大小排序,输出格式:颜色 出现段数
思路:同上题类似,数据只有8000可以不离散化处理。叶节点维护 [ x , x+1 ] 的颜色,根节点维护整个区间的颜色,当整个区间的颜色不一致时,区间不设颜色,即ST[ rt ]=-1
最后统计答案时,设置pre,表示上一个查询到的颜色,与当前查询到的颜色不同时,才会有段数的增加, mp[ ST[ rt ] ]++(mp是map,key是颜色,value是颜色的段数)
顺序就从左往右访问叶节点即可,也不是一定要访问到叶节点,碰到有颜色的根节点,可以直接返回。给出的代码是直接遍历到叶节点的,因为这样操作比较简单
1、非离散化写法
const int maxn=8000+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int n,l,r,c;int ST[maxn<<3],ans[maxn];
int pre;
void Push_down(int rt)
{
if(ST[rt]!=-1)
{
ST[ls]=ST[rt];
ST[rs]=ST[rt];
ST[rt]=-1;
}
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
ST[rt]=c;
return;
}
if(ST[rt]==c)
return;
Push_down(rt);
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
}
void Query(int rt,int L,int R)
{
if(L==R)
{
if(ST[rt]!=-1&&ST[rt]!=pre)
ans[ST[rt]]++;
pre=ST[rt];
return;
}
Push_down(rt);
int mid=(L+R)>>1;
Query(ls,L,mid);
Query(rs,mid+1,R);
}
int main()
{
while(~scanf("%d",&n))
{
mes(ST,-1);
mes(ans,0);
rep(i,1,n)
{
scanf("%d %d %d",&l,&r,&c);
Update(1,l+1,r,1,8000,c);
}
pre=-1;
Query(1,1,8000);
rep(i,0,8000)
if(ans[i])
printf("%d %d\n",i,ans[i]);
printf("\n");
}
return 0;
}
2、离散化写法
const int maxn=8000+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int n,l,r;int X[maxn<<1],ST[maxn<<2];
map<int,int> mp;int pre;
struct Line
{
int l,r,c;
}lines[maxn];
void Push_down(int rt)
{
if(ST[rt]!=-1)
{
ST[ls]=ST[rs]=ST[rt];
ST[rt]=-1;
}
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
ST[rt]=c;
return;
}
Push_down(rt);
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
}
void Query(int rt,int L,int R)
{
if(L==R)
{
if(ST[rt]!=-1&&pre!=ST[rt])
mp[ST[rt]]++;
pre=ST[rt];
return;
}
Push_down(rt);
int mid=(L+R)>>1;
Query(ls,L,mid);
Query(rs,mid+1,R);
}
void Query(int rt,int L,int R)
{
if(ST[rt]!=-1&&pre!=ST[rt])
{
mp[ST[rt]]++;
pre=ST[rt];
return;
}
if(L==R)
{
pre=ST[rt];
return;
}
Push_down(rt);
int mid=(L+R)>>1;
Query(ls,L,mid);
Query(rs,mid+1,R);
}
int main()
{
while(~scanf("%d",&n))
{
rep(i,1,n)
{
scanf("%d %d %d",&lines[i].l,&lines[i].r,&lines[i].c);
X[i*2-1]=lines[i].l;
X[i*2]=lines[i].r;
}
sort(X+1,X+1+n*2);
int k=unique(X+1,X+1+2*n)-X-1;
mp.clear();
mes(ST,-1);
for(int i=1;i<=n;++i)
{
int l=lower_bound(X+1,X+1+k,lines[i].l)-X;
int r=lower_bound(X+1,X+1+k,lines[i].r)-X-1;
if(l<=r)
Update(1,l,r,1,k-1,lines[i].c);
}
pre=-1;
Query(1,1,k-1);
map<int,int> ::iterator it;
for(it=mp.begin();it!=mp.end();it++)
printf("%d %d\n",it->first,it->second);
printf("\n");
}
return 0;
}
6、Tunnel Warfare HDU - 1540
类型:区间合并、单点更新、最大连续区间
题意:三种操作
D a:摧毁a
Q a:查询与a联通的村庄数量
R a:恢复上一个被摧毁的村庄
思路:每个叶节点维护三个值,ll左边连续区间大小、rl右边连续区间的大小,ml该区间的最大连续区间的大小。根节点也是同样维护这三个值
Build时整个区间是连续的,直接赋值为R-L+1即可。Update时,有两种单点更新的操作,摧毁或者恢复村子。更新完叶节点,处理回溯操作。Query时,返回它的最大连续区间的值ml即可,类比与求区间和,直接返回区间的大小。分三种情况,一种是在左子树,一种是在右子树,还有一种就是横跨两边。
const int maxn=5e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,M,p;char op[10];
int stack[maxn];
int k;
struct Seg_tree
{
int ll,rl,ml;
}ST[maxn<<2];
void Push_up(int rt,int L,int R)
{
ST[rt].ll=ST[ls].ll;
ST[rt].rl=ST[rs].rl;
int mid=(L+R)>>1;
if(ST[ls].ll==(mid-L+1))
ST[rt].ll+=ST[rs].ll;
if(ST[rs].rl==R-mid)
ST[rt].rl+=ST[ls].rl;
ST[rt].ml=max(max(ST[rt].ll,ST[rt].rl),ST[ls].rl+ST[rs].ll);
ST[rt].ml=max(max(ST[ls].ml,ST[rs].ml),ST[rt].ml);
}
void Build(int rt,int L,int R)
{
ST[rt].ll=R-L+1;
ST[rt].rl=R-L+1;
ST[rt].ml=R-L+1;
if(L==R)
return;
int mid=(L+R)>>1;
Build(ls,L,mid);
Build(rs,mid+1,R);
}
void Update(int rt,int p,int L,int R,int c)
{
if(L==R)
{
if(c)
{
ST[rt].ll=1;
ST[rt].rl=1;
ST[rt].ml=1;
}
else
{
ST[rt].ll=0;
ST[rt].rl=0;
ST[rt].ml=0;
}
return;
}
int mid=(L+R)>>1;
if(p<=mid)
Update(ls,p,L,mid,c);
if(p>mid)
Update(rs,p,mid+1,R,c);
Push_up(rt,L,R);
}
int Query(int rt,int p,int L,int R)
{
if(L==R||R-L+1==ST[rt].ml)
return ST[rt].ml;
int mid=(L+R)>>1;
if(p<=mid)
{
if(p>=mid-ST[ls].rl+1)
return Query(ls,p,L,mid)+Query(rs,mid+1,mid+1,R);
else
return Query(ls,p,L,mid);
}
if(p>mid)
{
if(p<=mid+1+ST[rs].ll-1)
return Query(ls,mid,L,mid)+Query(rs,p,mid+1,R);
else
return Query(rs,p,mid+1,R);
}
}
int main()
{
while(~scanf("%d %d",&N,&M))
{
Build(1,1,N);
k=0;
while(M--)
{
scanf("%s",op);
if(op[0]=='D')
{
scanf("%d",&p);
Update(1,p,1,N,0);
stack[++k]=p;
}
else if(op[0]=='Q')
{
scanf("%d",&p);
printf("%d\n",Query(1,p,1,N));
}
else if(op[0]=='R')
{
p=stack[k--];
Update(1,p,1,N,1);
}
}
}
return 0;
}
思路二:用maxx表示区间内被破坏村子最大的序号,用minn表示区间内被破坏村子最小的序号。叶节点维护最大值maxx和最小值minn,恢复村子时maxx=0,minn=n+1,破坏村子时maxx=c,minn=c。根节点维护区间的最大值maxx和最小值minn
每当查询一个点时,就查询该点左边区间的最大的被破坏村子maxx,以及该点右边的最小的被破坏的村子minn。统计答案,就是minn-maxx-1,当然如果是自己被破坏了答案就是minn-maxx
类型:单点更新
const int maxn=5e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,M;char op[10];
int stack[maxn];
int k,p;
struct Seg_tree
{
int maxx,minn;
}ST[maxn<<2];
void Push_up(int rt)
{
ST[rt].maxx=max(ST[ls].maxx,ST[rs].maxx);
ST[rt].minn=min(ST[ls].minn,ST[rs].minn);
}
void Build(int rt,int L,int R)
{
if(L==R)
{
ST[rt].maxx=0;
ST[rt].minn=N+1;
return;
}
int mid=(L+R)>>1;
Build(ls,L,mid);
Build(rs,mid+1,R);
Push_up(rt);
}
void Update(int rt,int p,int L,int R,int c)
{
if(L==R)
{
if(c)
{
ST[rt].maxx=c;
ST[rt].minn=c;
}
else
{
ST[rt].maxx=0;
ST[rt].minn=N+1;
}
return;
}
int mid=(L+R)>>1;
if(p<=mid)
Update(ls,p,L,mid,c);
if(p>mid)
Update(rs,p,mid+1,R,c);
Push_up(rt);
}
int QueryMin(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt].minn;
int mid=(L+R)>>1;
int minn=INF;
if(l<=mid)
minn=min(QueryMin(ls,l,r,L,mid),minn);
if(r>mid)
minn=min(QueryMin(rs,l,r,mid+1,R),minn);
return minn;
}
int QueryMax(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt].maxx;
int mid=(L+R)>>1;
int maxx=-INF;
if(l<=mid)
maxx=max(QueryMax(ls,l,r,L,mid),maxx);
if(r>mid)
maxx=max(QueryMax(rs,l,r,mid+1,R),maxx);
return maxx;
}
int main()
{
while(~scanf("%d %d",&N,&M))
{
Build(1,1,N);
k=0;
while(M--)
{
scanf("%s",op);
if(op[0]=='D')
{
scanf("%d",&p);
Update(1,p,1,N,p);
stack[++k]=p;
}
else if(op[0]=='R')
{
p=stack[k--];
Update(1,p,1,N,0);
}
else if(op[0]=='Q')
{
scanf("%d",&p);
int temp=QueryMin(1,p,N,1,N)-QueryMax(1,1,p,1,N);
if(temp)
temp--;
printf("%d\n",temp);
}
}
}
return 0;
}
Assign the task HDU - 3974
题意:给出一棵树,对父节点染色,相当于对所有的子节点染色
两种操作
C x:表示查询x点的颜色
T x y:把x点的颜色变为y
思路:题目中给出了树的结构,可以用dfs序来维护子节点范围。用线段树叶节点维护树中的每一个节点的颜色,根节点维护区间的颜色
类型:线段树维护dfs序
补充知识:dfs序
void dfs(int u)
{
Start[u]=++time;
for(int i=0;i<G[u].size();++i)
{
int v=G[u][i];
if(!visit[v])
{
visit[v]=1;
dfs(v);
}
}
End[u]=time;
}
dfs序的时间戳,进栈的时候时间戳+1,出栈的时候时间戳保持不变。dfs序可以用来维护某节点所管理的子节点的范围
1号节点的时间戳是 4 --> 4
2号节点的时间戳是 1 --> 5
(1)其中1代表2号节点的进栈时间Start[2],5代表2号节点出栈的时间End[2]。
(2)进栈的时候时间戳会+1,1表示它是第1个进栈的
(3)2 --> 5代表第2个节点到第5个节点都是它的子孙节点
(4)对于线段树的区间[ 1 , 5 ],我们应该明确的是[ 1 , 5 ],实际上是对1、2、3、4、5这5个叶节点的维护。
用线段树维护dfs序,其实也就是 一个线段树中的叶节点对应一个dfs序中的节点。所以如果对2号节点染色,相当于对自己和自己的子孙节点染色,也就是对1 --> 5 这5个点染色。所以dfs序的进栈和出栈的时间戳1 --> 5,也就对应于线段树的区间 [ 1 , 5 ]
const int maxn=5e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,M,N;int u,v,time;
int Start[maxn],End[maxn];
int visit[maxn];
vector<int> G[maxn];
char op[10];
int p,c;
int ST[maxn<<2];
void dfs(int v)
{
Start[v]=++time;
for(int i=0;i<G[v].size();++i)
{
int u=G[v][i];
if(!visit[u])
{
visit[u]=1;
dfs(u);
}
}
End[v]=time;
}
void Push_down(int rt)
{
if(ST[rt]!=-1)
{
ST[ls]=ST[rs]=ST[rt];
ST[rt]=-1;
}
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
ST[rt]=c;
return;
}
Push_down(rt);
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
}
int Query(int rt,int l,int r,int L,int R)
{
if(L==R&&L==l)
return ST[rt];
Push_down(rt);
int mid=(L+R)>>1;
if(l<=mid)
return Query(ls,l,r,L,mid);
if(r>mid)
return Query(rs,l,r,mid+1,R);
}
int main()
{
int Cas=0;
scanf("%d",&T);
while(T--)
{
mes(visit,0);
mes(ST,-1);
scanf("%d",&N);
rep(i,1,N)
G[i].clear();
rep(i,1,N-1)
{
scanf("%d %d",&u,&v);
G[v].pb(u);
visit[u]=1;
}
time=0;
rep(i,1,N)
{
if(!visit[i])
{
mes(visit,0);
dfs(i);
break;
}
}
scanf("%d",&M);
printf("Case #%d:\n",++Cas);
while(M--)
{
scanf("%s",&op);
if(op[0]=='C')
{
scanf("%d",&p);
printf("%d\n",Query(1,Start[p],End[p],1,N));
}
else if(op[0]=='T')
{
scanf("%d %d",&p,&c);
Update(1,Start[p],End[p],1,N,c);
}
}
}
return 0;
}
9、Vases and Flowers HDU - 4614
题意:两种操作
1 A F:从第A个花瓶开始插入F朵花,如果瓶子有花了,就往后插入花,直到插完所有的花,或者没有瓶子插入花为止。输出插花的起点和终点
2 A B:清空第A个花瓶到第B个花瓶中的花,并输出清空的花的数量
思路:每个叶节点维护一个空瓶,1表示是空瓶,0表示有花了。根节点维护空瓶的数量,之所以这样设置,是为了便于根据指定范围查询空瓶的数量,对于操作1可以直接从A点开始,二分查找刚好F个空瓶的位置。对于操作2,可以直接查找区间[ A , B ]内空瓶的数量
过程:
1、先判断区间[ A , N ]内有没有空瓶,没有空瓶的话,输出 Can not put any one.
2、然后,空瓶的数量和放花的数量取小者,二分查找放置的起点和终点
题目中花瓶的范围是0n-1,而习惯于改成1n
总结:个人感觉这里的难点是有花的瓶子在哪里是不确定的,但是这里通过二分法,准确的找到了有F个空瓶的区间,避开了具体哪个是空瓶,哪个是有花的瓶子
补充知识:二分查找
在二分搜索中l或r不要出现mid-1这种形式,很容易陷在递归中无限循环
单调递增时,f(mid)>=k,r=mid。mid是终点,k所求解(x)对应的函数值(y)
单调递减时,f(mid)<=k,r=mid。mid是终点,k所求解(x)对应的函数值(y)
单调递增
while(l<r)
{
int mid=(l+r)>>1;
if(f(mid)>=k)
r=mid;
else
l=mid+1;
}
while(l<r)
{
int mid=(l+r)>>1;
if(f(mid)<k)
l=mid+1;
else
r=mid;
}
单调递减
while(l<r)
{
int mid=(l+r)>>1;
if(f(mid)<=k)
r=mid;
else
l=mid+1;
}
while(l<r)
{
int mid=(l+r)>>1;
if(f(mid)>k)
l=mid+1;
else
r=mid;
}
const int maxn=5e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,N,M;
int op,x,y;
int lazy[maxn<<2],ST[maxn<<2];
void Push_up(int rt)
{
ST[rt]=ST[ls]+ST[rs];
}
void Push_down(int rt,int L,int R)
{
if(lazy[rt]!=-1)
{
lazy[ls]=lazy[rs]=lazy[rt];
int mid=(L+R)>>1;
ST[ls]=(mid-L+1)*lazy[rt];
ST[rs]=(R-mid)*lazy[rt];
lazy[rt]=-1;
}
}
void Build(int rt,int L,int R)
{
lazy[rt]=-1;
if(L==R)
{
ST[rt]=1;
return;
}
int mid=(L+R)>>1;
Build(ls,L,mid);
Build(rs,mid+1,R);
Push_up(rt);
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
lazy[rt]=c;
ST[rt]=(R-L+1)*c;
return;
}
Push_down(rt,L,R);
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
Push_up(rt);
}
int Query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt];
Push_down(rt,L,R);
int ans=0;
int mid=(L+R)>>1;
if(l<=mid)
ans+=Query(ls,l,r,L,mid);
if(r>mid)
ans+=Query(rs,l,r,mid+1,R);
return ans;
}
int BinarySearch(int left,int num)
{
int l=left,r=N;
while(l<r)
{
int mid=(l+r)>>1;
if(Query(1,left,mid,1,N)<num)
l=mid+1;
else
r=mid;
}
return r;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&N,&M);
Build(1,1,N);
while(M--)
{
scanf("%d %d %d",&op,&x,&y);
if(op==1)
{
x++;
int cnt=Query(1,x,N,1,N);
if(cnt==0)
{
printf("Can not put any one.\n");
continue;
}
int l=BinarySearch(x,1);
int r=BinarySearch(x,min(y,cnt));
printf("%d %d\n",l-1,r-1);
Update(1,l,r,1,N,0);
}
else if(op==2)
{
x++;
y++;
printf("%d\n",y-x+1-Query(1,x,y,1,N));
Update(1,x,y,1,N,1);
}
}
printf("\n");
}
return 0;
}
1、Atlantis HDU - 1542
题意:求平面矩阵的覆盖面积
思路:离散化X轴,用lower_bound和upper_bound两个函数查找原x坐标对应的索引。每个叶节点维护离散化之后的小区间[ x[ i ] , x[ i+1 ] ]是否被覆盖,以及被覆盖的长度X[R+1]-X[L],根节点维护大区间被覆盖的长度。线段树第一个根节点ST[1].length,表示的是整个区间被覆盖的长度,此时累加 高度之差*ST[1].length 就可以得到最终的覆盖面积
关于Push_up函数:
关键在于回溯,先判断是否覆盖,如果覆盖了直接计算长度
如果没覆盖,且是叶节点,那就更新为0
如果都不是,表明是没被完全覆盖的根节点,长度为两个子节点的覆盖的长度。这里,子节点的覆盖长度,是通过其它下位线更新得到的,也就是在扫到这条扫描线之前,对该子节点已经做过更新。此时就可以直接回溯。
一个子节点的覆盖次数,是所有祖先的覆盖次数的总和。因为代码中是对区间做的更新,因此更新到完全覆盖的根节点,就不会再往下更新了,此时的子节点是被完全覆盖的
类型:扫描线、离散化
补充知识:扫描线可以计算矩形面积、周长,可以计算线段交点,可以实现多边形扫描转换,在图的处理方面经常用到
离散化知识:
离散与不离散的区别
不离散的时候是一个一个的点。为了表示scanlines[i].l和scanlines[i].r
函数的写法是:Update(1,scanlines[i].l,scanlines[i].r-1,scanlines[i].f);
离散的时候是一个又一个的段
写法是: int l=lower_bound(X+1,X+1+k,scanlines[i].l)-X;
int r=lower_bound(X+1,X+1+k,scanlines[i].r)-X-1;
if(l<=r)
Update(1,l,r,1,k-1,scanlines[i].f);
离散化操作
1、对n个元素的X数组去重后,有k个元素
int k=1;
for(int i=2;i<=n;++i)
if(X[i]!=X[i-1])
X[++k]=X[i];
2、更简单的去重方法,这样X数组变式去重后的数组
int tot=unique(X+1,X+1+2*N)-X-1;
在去重后的数组中:两个相同的二分查找
int l=lower_bound(X+1,X+1+tot,scanlines[i].l)-X;
int r=lower_bound(X+1,X+1+tot,scanlines[i].r)-X-1;
int l=upper_bound(X+1,X+1+tot,scanlines[i].l)-&X[1];
int r=upper_bound(X+1,X+1+tot,scanlines[i].r)-&X[1]-1;
在不去重的数组中,查找一段的方法
int l=lower_bound(A+1,A+1+n,l)-A;
int r=upper_bound(A+1,A+1+n,r)-A-1;
const int maxn=100+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
double X[maxn<<1];
int N;
struct Seg_tree
{
int cover;
double length;
}ST[maxn<<3];
struct ScanLine
{
double l,r,h;
int f;
ScanLine(double l1,double r1,double h1,int f1):l(l1),r(r1),h(h1),f(f1)
{
}
ScanLine()
{
}
bool operator<(const ScanLine& b) const
{
return h<b.h;
}
}scanlines[maxn<<1];
void Push_up(int rt,int L,int R)
{
if(ST[rt].cover)
{
ST[rt].length=X[R+1]-X[L];
}
else if(L==R)
{
ST[rt].length=0;
}
else
{
ST[rt].length=ST[ls].length+ST[rs].length;
}
}
void Update(int rt,int l,int r,int L,int R,int c){
if(l<=L&&R<=r)
{
ST[rt].cover+=c;
Push_up(rt,L,R);
return;
}
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
Push_up(rt,L,R);
}
int main()
{
double x1,x2,y1,y2;
int Cas=0;
while(scanf("%d",&N)&&N)
{
rep(i,1,N)
{
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
scanlines[i*2-1]=ScanLine(x1,x2,y1,1);
scanlines[i*2]=ScanLine(x1,x2,y2,-1);
X[i*2-1]=x1;
X[i*2]=x2;
}
sort(X+1,X+1+2*N);
sort(scanlines+1,scanlines+1+2*N);
int k=unique(X+1,X+1+2*N)-X-1;
mes(ST,0);
double ans=0;
for(int i=1;i<=2*N;++i)
{
int l=lower_bound(X+1,X+1+k,scanlines[i].l)-X;
int r=lower_bound(X+1,X+1+k,scanlines[i].r)-X-1;
if(l<=r)
Update(1,l,r,1,k-1,scanlines[i].f);
ans+=(scanlines[i+1].h-scanlines[i].h)*ST[1].length;
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",
++Cas,ans);
}
return 0;
}
2、Picture POJ - 1177
题意:给出矩形的坐标,求所有矩形覆盖后的周长
思路:分两块计算
横线的长度,就是每次扫描线扫过之后的覆盖长度减去上一次的长度的绝对值
竖线的长度,就统计两条扫描线之间矩阵的个数,两条扫描线的高度矩阵个数2
因此,题目转化为求两条扫描线之间矩阵的个数,而矩阵是上下对称的,也就是求一条扫描线上不连续线段的个数(num)
每个节点维护五个值:length表示区间长度、cover覆盖次数、num不连续线段数量、lc左端点是否覆盖、rc右端点是否覆盖
const int maxn=5000+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int n,X[maxn<<1];
struct ScanLine
{
double l,r,h;
int f;
ScanLine(double l1,double r1,double h1,int f1):l(l1),r(r1),h(h1),f(f1)
{
}
ScanLine()
{
}
bool operator<(const ScanLine&b) const
{
return h<b.h;
}
}scanlines[maxn<<1];
struct Seg_tree
{
int length,num,cover;
bool lc,rc;
}ST[maxn<<2];
void Push_up(int rt,int L,int R)
{
if(ST[rt].cover)
{
ST[rt].length=X[R+1]-X[L];
ST[rt].lc=ST[rt].rc=1;
ST[rt].num=1;
}
else if(L==R)
{
ST[rt].length=0;
ST[rt].lc=ST[rt].rc=0;
ST[rt].num=0;
}
else
{
ST[rt].length=ST[ls].length+ST[rs].length;
ST[rt].lc=ST[ls].lc;
ST[rt].rc=ST[rs].rc; ST[rt].num=ST[ls].num+ST[rs].num-(ST[ls].rc&ST[rs].lc);
}
}
void Build(int rt,int L,int R)
{
ST[rt].cover=0;
ST[rt].lc=ST[rt].rc=0;
ST[rt].length=0;
ST[rt].num=0;
if(L==R)
return;
int mid=(L+R)>>1;
Build(ls,L,mid);
Build(rs,mid+1,R);
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
ST[rt].cover+=c;
Push_up(rt,L,R);
return;
}
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
Push_up(rt,L,R);
}
int main()
{
while(~scanf("%d",&n)&&n)
{
double x1,y1,x2,y2;
rep(i,1,n)
{
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
scanlines[i*2-1]=ScanLine(x1,x2,y1,1);
scanlines[i*2]=ScanLine(x1,x2,y2,-1);
X[i*2-1]=x1;
X[i*2]=x2;
}
sort(X+1,X+1+n*2);
sort(scanlines+1,scanlines+1+n*2);
int k=unique(X+1,X+1+2*n)-X-1;
Build(1,1,k-1);
int ans=0,pre=0;
for(int i=1;i<=2*n;++i)
{
int l=lower_bound(X+1,X+1+k,scanlines[i].l)-X;
int r=lower_bound(X+1,X+1+k,scanlines[i].r)-X-1;
if(l<=r)
Update(1,l,r,1,k-1,scanlines[i].f);
ans+=abs(ST[1].length-pre); ans+=(scanlines[i+1].h-scanlines[i].h)*ST[1].num*2;
pre=ST[1].length;
}
printf("%d\n",ans);
}
return 0;
}
3、覆盖的面积 HDU - 1255
题意:给出多个矩阵,求矩阵中覆盖两次以上的面积
思路:每个节点维护三个值,once至少覆盖一次的长度、twice至少覆盖两次的长度、cover覆盖次数。其实,分类的方法有多种,关键是选择一个标准讨论
const int maxn=1e3+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,N;double X[maxn<<1];
struct Seg_tree
{
double once,twice;
int cover;
}ST[maxn<<3];
struct ScanLine
{
double l,r,h;
int f;
ScanLine(double l1,double r1,double h1,int f1):l(l1),r(r1),h(h1),f(f1)
{
}
ScanLine()
{
}
bool operator<(const ScanLine& b) const
{
return h<b.h;
}
}scanlines[maxn<<1];
void Push_up(int rt,int L,int R)
{
if(ST[rt].cover>=2)
ST[rt].twice=X[R+1]-X[L];
else if(L==R)
ST[rt].twice=0;
else if(ST[rt].cover==1)
ST[rt].twice=ST[ls].once+ST[rs].once;
else
ST[rt].twice=ST[ls].twice+ST[rs].twice;
if(ST[rt].cover)
ST[rt].once=X[R+1]-X[L];
else if(L==R)
ST[rt].once=0;
else
ST[rt].once=ST[ls].once+ST[rs].once;
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
ST[rt].cover+=c;
Push_up(rt,L,R);
return;
}
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
Push_up(rt,L,R);
}
int main()
{
double x1,y1,x2,y2;
scanf("%d",&T);
while(T--)
{
scanf("%d",&N);
rep(i,1,N)
{
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
scanlines[i*2-1]=ScanLine(x1,x2,y1,1);
scanlines[i*2]=ScanLine(x1,x2,y2,-1);
X[i*2-1]=x1;
X[i*2]=x2;
}
sort(X+1,X+1+2*N);
sort(scanlines+1,scanlines+1+2*N);
int k=unique(X+1,X+1+2*N)-X-1;
mes(ST,0);
double ans=0;
for(int i=1;i<=2*N;++i)
{
int l=lower_bound(X+1,X+1+k,scanlines[i].l)-X;
int r=lower_bound(X+1,X+1+k,scanlines[i].r)-X-1;
if(l<=r)
Update(1,l,r,1,k-1,scanlines[i].f);
ans+=(scanlines[i+1].h-scanlines[i].h)*ST[1].twice;
}
printf("%.2lf\n",ans);
}
return 0;
}
4、Get The Treasury HDU - 3642
题意:求长方体中覆盖三次以上的体积
思路:体积覆盖3次转化为面积覆盖3次。离散化Z轴,在水平方向上求出面积覆盖3次以上的总面积。离散化Z轴,对每个体积包含[ Z[i] , Z[i+1] ]的立方体,抽出两条平面上的扫描线。利用这些线,先计算该区间内覆盖三次以上的水平面积,然后乘上 Z[ i+1 ] - Z[ i ]
其次,对于覆盖三次以上的面积的求解,分类方法挺多的。代码中采用的是第二种
once 整个区间覆盖一次 整个区间至少覆盖一次 区间内只覆盖一次
twice 整个区间覆盖两次 整个区间至少覆盖两次 区间内只覆盖两次
more 整个区间覆盖多次 整个区间至少覆盖多次 区间内覆盖多次
const int maxn=1e3+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,N;
int Z[maxn<<1],X[maxn<<1];
struct Seg_tree
{
int once,twice,more;
int cover;
}ST[maxn<<3];
struct ScanLine
{
double l,r,h;
int f;
ScanLine(double l1,double r1,double h1,int f1):l(l1),r(r1),h(h1),f(f1)
{
}
ScanLine()
{
}
bool operator<(const ScanLine & b) const
{
if(b.h==h)
return f>b.f;
return h<b.h;
}
}scanlines[maxn<<1];
struct Cuboid
{
int x1,y1,z1,x2,y2,z2;
}cuboids[maxn<<1];
void Push_up(int rt,int L,int R)
{
if(ST[rt].cover>=3)
{
ST[rt].once=X[R+1]-X[L];
ST[rt].twice=X[R+1]-X[L];
ST[rt].more=X[R+1]-X[L];
}
else if(ST[rt].cover==2)
{
ST[rt].more=ST[ls].once+ST[rs].once;
ST[rt].once=X[R+1]-X[L];
ST[rt].twice=X[R+1]-X[L];
}
else if(ST[rt].cover==1)
{
ST[rt].more=ST[ls].twice+ST[rs].twice;
ST[rt].once=X[R+1]-X[L];
ST[rt].twice=ST[ls].once+ST[rs].once;
}
else if(L==R)
{
ST[rt].more=0;
ST[rt].once=0;
ST[rt].twice=0;
}
else
{
ST[rt].more=ST[ls].more+ST[rs].more;
ST[rt].once=ST[ls].once+ST[rs].once;
ST[rt].twice=ST[ls].twice+ST[rs].twice;
}
}
void Update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
ST[rt].cover+=c;
Push_up(rt,L,R);
return;
}
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid,c);
if(r>mid)
Update(rs,l,r,mid+1,R,c);
Push_up(rt,L,R);
}
int main()
{
scanf("%d",&T);
int Cas=0;
while(T--)
{
scanf("%d",&N);
rep(i,1,N)
{
scanf("%d %d %d %d %d %d",&cuboids[i].x1,&cuboids[i].y1,&cuboids[i].z1,
&cuboids[i].x2,&cuboids[i].y2,&cuboids[i].z2);
Z[i*2-1]=cuboids[i].z1;
Z[i*2]=cuboids[i].z2;
}
sort(Z+1,Z+1+2*N);
int k=unique(Z+1,Z+1+2*N)-Z-1;
ll ans=0;
for(int i=1;i<k;++i)
{
int k1=0,k2=0;
for(int j=1;j<=N;++j)
{
if(cuboids[j].z1<=Z[i]&&cuboids[j].z2>=Z[i+1])
{
scanlines[++k1]=ScanLine(cuboids[j].x1,cuboids[j].x2,cuboids[j].y1,1);
scanlines[++k1]=ScanLine(cuboids[j].x1,cuboids[j].x2,cuboids[j].y2,-1);
X[++k2]=cuboids[j].x1;
X[++k2]=cuboids[j].x2;
}
}
sort(scanlines+1,scanlines+1+k1);
sort(X+1,X+1+k2);
int k3=unique(X+1,X+1+k2)-X-1;
mes(ST,0);
for(int j=1;j<=k1;++j)
{
int l=lower_bound(X+1,X+1+k3,scanlines[j].l)-X;
int r=lower_bound(X+1,X+1+k3,scanlines[j].r)-X-1;
if(l<=r)
Update(1,l,r,1,k3-1,scanlines[j].f);
ans+=1ll*(scanlines[j+1].h-scanlines[j].h)*ST[1].more*(Z[i+1]-Z[i]);
}
}
printf("Case %d: %lld\n",++Cas,ans);
}
return 0;
}
5、Black and White UVALive - 8356
题意:给定一个N*N的矩阵,给出一些矩阵的翻转操作,黑变白或者白变黑,求最后的覆盖为黑色的区域面积
思路:用线段树维护翻转情况。每一个叶节点维护一层中每一格的翻转情况,黑为1,白为0。根节点维护总区间黑色的数量。
扫描线从下往上一层一层的扫描,先进行下位线的翻转操作,统计答案后,再进行上位线的操作取消某操作的影响
const int maxn=1e4+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int T,N,K;
int ST[maxn<<2],lazy[maxn<<2];
struct ScanLine
{
int l,r,h,f;
ScanLine(int l1,int r1,int h1,int f1):l(l1),r(r1),h(h1),f(f1)
{
}
ScanLine()
{
}
bool operator<(const ScanLine & b) const
{
if(h==b.h)
return f>b.f;
return h<b.h;
}
}scanlines[maxn<<1];
void Push_up(int rt)
{
ST[rt]=ST[ls]+ST[rs];
}
void Push_down(int rt,int L,int R)
{
if(lazy[rt])
{
lazy[ls]^=1;
lazy[rs]^=1;
int mid=(L+R)>>1;
ST[ls]=mid-L+1-ST[ls];
ST[rs]=R-mid-ST[rs];
lazy[rt]=0;
}
}
void Update(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
{
lazy[rt]^=1;
ST[rt]=R-L+1-ST[rt];
return;
}
Push_down(rt,L,R);
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid);
if(r>mid)
Update(rs,l,r,mid+1,R);
Push_up(rt);
}
int main()
{
int x1,y1,x2,y2;
scanf("%d",&T);
while(T--)
{
mes(ST,0);
mes(lazy,0);
scanf("%d %d",&N,&K);
rep(i,1,K)
{
scanf("%d %d %d %d",&x1,&x2,&y1,&y2);
scanlines[i*2-1]=ScanLine(x1,x2,y1,1);
scanlines[i*2]=ScanLine(x1,x2,y2,-1);
}
sort(scanlines+1,scanlines+1+2*K);
int ans=0;
int p=1;
for(int i=1;i<=N;++i)
{
while(scanlines[p].h==i&&scanlines[p].f==1)
{
Update(1,scanlines[p].l,scanlines[p].r,1,N);
p++;
}
ans+=ST[1];
while(scanlines[p].h==i&&scanlines[p].f==-1)
{
Update(1,scanlines[p].l,scanlines[p].r,1,N);
p++;
}
}
printf("%d\n",ans);
}
return 0;
}
Tokitsukaze and Strange Rectangle
题意:给出n个点,用一个三根线组成的u型框,去包含其中的多个点,问有多少种不同的选法
思路:从上而下、从左往右,扫描每一个点,把同一行的点放在栈中,统一计算。在同一层中(y相同),每次多加入一个点之后的情况
1、如果是该层的第一个点,新增的答案数量是该点左上方点的个数,加上自己
2、如果是该层最后一个点(自己加的辅助的点),新增的答案数量是 前一个点左上方点的个数(包括自己)乘以 前一个点到该点左上方点的个数(不包括自己)
3、既不是第一也不是最后的点,新增数量为,前一个点左上方点的个数(包括自己)乘以 前一个点到该点左上方点的个数(包括自己)加上 前一个点到该点左上方点的个数(包括自己)
每个叶节点维护一个离散化之后的区间([ x[ i ] , x[ i ] + 1 ])上点是否扫描到,根节点维护区间上点的总数量
const int maxn=2e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
ll N,ST[maxn<<2];
ll X[maxn],Y[maxn];
struct Point
{
int x,y;
bool operator<(const Point & b) const
{
if(y==b.y)
return x<b.x;
return y>b.y;
}
}p[maxn];
void Push_up(int rt)
{
ST[rt]=ST[ls]+ST[rs];
}
void Update(int rt,int p,int L,int R,int c)
{
if(L==R)
{
ST[rt]=c;
return;
}
int mid=(L+R)>>1;
if(p<=mid)
Update(ls,p,L,mid,c);
if(p>mid)
Update(rs,p,mid+1,R,c);
Push_up(rt);
}
ll Query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt];
ll ans=0;
int mid=(L+R)>>1;
if(l<=mid)
ans+=Query(ls,l,r,L,mid);
if(r>mid)
ans+=Query(rs,l,r,mid+1,R);
return ans;
}
int main()
{
scanf("%d",&N);
rep(i,1,N)
{
scanf("%d %d",&p[i].x,&p[i].y);
X[i]=p[i].x;
Y[i]=p[i].y;
}
sort(X+1,X+1+N);
sort(Y+1,Y+1+N);
int k1=unique(X+1,X+1+N)-(X+1);
int k2=unique(Y+1,Y+1+N)-(Y+1);
rep(i,1,N)
{
p[i].x=lower_bound(X+1,X+1+k1,p[i].x)-X;
p[i].y=lower_bound(Y+1,Y+1+k2,p[i].y)-Y;
}
sort(p+1,p+1+N);
ll sum=0,cur=1;
int stack[maxn];
int i=1;
int z;
Update(1,k1+1,1,k1+1,1);
while(i<=N)
{
int top=0;
while(p[i].y==p[cur].y)
{
Update(1,p[i].x,1,k1+1,1);
stack[++top]=p[i].x;
i++;
}
stack[++top]=k1+1;
ll cnt=Query(1,1,stack[1],1,k1+1);
sum+=cnt;
for(int j=2;j<=top;++j)
{
ll l=Query(1,1,stack[j-1],1,k1+1);
ll r=Query(1,stack[j-1]+1,stack[j],1,k1+1);
if(j==top)
sum+=l*(r-1);
else
sum+=l*r+r;
}
cur=i;
}
printf("%lld\n",sum);
return 0;
}
1、Cow Tennis Tournament CodeForces - 283E
题意:有n个能力值,能力值越大越强,m次翻转。每次翻转,区间[a,b]内的能力颠倒。求解有多少个三元组(p,q,r),p打败q,q打败r,r打败p
思路:逆向思考,不符合三元组的就是其中有一个人打败了两个人。因此答案就是
其中ak是第k个人能够打败的人数
因此,题目转变为求每个人能够打败的人数。而每一个人,能够打败的是,在他左边变化偶数次的人,和在他右边变化奇数次的人。
同时对于一个区间[a,b]来说,在b的右边的能力是不受这个区间翻转的影响的。如果b右边未做过翻转,那么b右边的能力依旧是大于b左边的所有能力。因此每次统计完后,要进行以b为终点翻转操作,以取消影响对b右边能力的影响
同样用用线段树维护翻转操作。每一个叶节点维护每一个能力的翻转情况,1代表翻转数次,0代表翻转偶数次。根节点维护总区间翻转奇数次的数量
const int maxn=1e5+5,INF=0x3f3f3f3f;
const int mod=1e9+7;
int N,K,A[maxn];
int ST[maxn<<2],lazy[maxn<<2];
vector<int> vl[maxn],vr[maxn];
void Push_up(int rt)
{
ST[rt]=ST[ls]+ST[rs];
}
void Push_down(int rt,int L,int R)
{
if(lazy[rt])
{
lazy[ls]^=1;
lazy[rs]^=1;
int mid=(L+R)>>1;
ST[ls]=mid-L+1-ST[ls];
ST[rs]=R-mid-ST[rs];
lazy[rt]=0;
}
}
void Update(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
{
lazy[rt]^=1;
ST[rt]=R-L+1-ST[rt];
return;
}
Push_down(rt,L,R);
int mid=(L+R)>>1;
if(l<=mid)
Update(ls,l,r,L,mid);
if(r>mid)
Update(rs,l,r,mid+1,R);
Push_up(rt);
}
int Query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt];
Push_down(rt,L,R);
int ans=0;
int mid=(L+R)>>1;
if(l<=mid)
ans+=Query(ls,l,r,L,mid);
if(r>mid)
ans+=Query(rs,l,r,mid+1,R);
return ans;
}
int main()
{
int l,r;
while(~scanf("%d %d",&N,&K))
{
rep(i,1,N)
scanf("%d",&A[i]);
sort(A+1,A+1+N);
rep(i,1,K)
{
scanf("%d %d",&l,&r);
l=lower_bound(A+1,A+1+N,l)-A;
r=upper_bound(A+1,A+1+N,r)-A-1;
if(l<=r)
{
vl[l].pb(r);
vr[r].pb(l);
}
}
ll ans=1ll*N*(N-1)*(N-2)/6;
int p1,p2;
for(int i=1;i<=N;++i)
{
p1=0,p2=0;
while(p1<vl[i].size())
{
Update(1,i,vl[i][p1],1,N);
p1++;
}
int beat=0;
if(i>1)
beat+=i-1-Query(1,1,i-1,1,N);
if(i<N)
beat+=Query(1,i+1,N,1,N);
ans-=1ll*beat*(beat-1)/2;
while(p2<vr[i].size())
{
Update(1,vr[i][p2],i,1,N);
p2++;
}
}
printf("%lld\n",ans);
}
return 0;
}