hdu 3577 线段树,成段更新 好题 查询区间的最大覆盖次数
题目抽象:给定最大的覆盖次数k和q条线段
对于每条线段判断是否能覆盖在相应区间上使得该区间上最大的覆盖次数<k,输出这些线段的编号
有两种做法,一种使用懒惰标记,一种不使用,时间差不多
先讲使用懒惰标记的
没什么好讲的,在需要更新或查询的时候将信息往下传即可,上代码
#include<stdio.h>
#include<cstring>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define mid (l+r)>>1
const int maxn = 1000010;
int sum[maxn<<2];//子树区间内最大的覆盖次数
int col[maxn<<2];//lazy tag
int k,res[100010];
int max(int a,int b){
return a>b?a:b;
}
void pushup(int rt){
sum[rt]=max(sum[rt<<1],sum[rt<<1|1]);
}
void pushdown(int rt){
if(col[rt]){
sum[rt<<1]+=col[rt];
sum[rt<<1|1]+=col[rt];
col[rt<<1]+=col[rt];
col[rt<<1|1]+=col[rt];
col[rt]=0;
}
}
void update(int L,int R,int c,int l,int r,int rt){
if(L<=l&&r<=R){
col[rt]+=c;
sum[rt]+=c;
return ;
}
pushdown(rt);
int m=mid;
if(L<=m) update(L,R,c,lson);
if(R>m) update(L,R,c,rson);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){
return sum[rt];
}
pushdown(rt);
int m=mid, ret=0;
if(L<=m) ret=max(ret,query(L,R,lson));
if(R>m) ret=max(ret,query(L,R,rson));
return ret;
}
int main(){
int t,i,j,q,a,b,cases=1,tot;
scanf("%d",&t);
while(t--){
tot=0;
memset(col,0,sizeof(col));
memset(sum,0,sizeof(sum));
scanf("%d%d",&k,&q);
for(i=1;i<=q;i++){
scanf("%d%d",&a,&b);b--;
if(query(a,b,1,1000000,1)<k){
res[++tot]=i;
update(a,b,1,1,1000000,1);
}
}
printf("Case %d:\n",cases++);
for(i=1;i<=tot;i++){
printf("%d ",res[i]);
}
printf("\n\n");
}
}
下面一种是没有懒惰标记的线段树。
每条线段,有两个域: 本线段被覆盖的次数cnt, 其子树的最大被覆盖次数sum。
每次插入线段后返回的时候,就是子线段的cnt值改变后,返回时更新其每一层父结点的sum值....
而查询[c,d]的覆盖次数的时候,线段cd的最大覆盖次数 == 其各层父结点的cnt值的和+ 自身cnt+子线段最大覆盖次数sum
这样在查询时就可以保证在符合范围的区间返回,而不用到叶子,节省了时间。
就理解上面这句话理解了半天,后来画了画线段树的那棵树,把样例模拟了一下,终于搞懂了。。
每次先判断某个询问的区间最大的覆盖次数是否小于k(保证插入后小于等于k)
然后是插入操作,跟一般的成段更新一样,如果能完全覆盖当前区间,直接改变当前区间上的键值,就不再往下更新了,在此题中即sum和cnt都加1
现在假如要查询某区间的覆盖次数,从根节点开始往下,把经过的节点的cnt值都加上(包括自己),然后再加上下面的子树的最大覆盖次数
每次更新当前节点的sum值的pushup是把子节点的最大值传上来,再加上自己被覆盖的次数
举个简单的例子
假设整个区间是【1,6】,现在先覆盖【1,6】一次,再覆盖【4,6】一次,则如果要查询【4,6】区间的最大覆盖的次数,就是1(1,6)+1(4,6);因为第一次更新在【1,6】后就再也没有往下更新
贴一下错了很多次后的的代码
#include<stdio.h>
#include<cstring>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define mid (l+r)>>1
const int maxn = 1000010;
int sum[maxn<<2];//子树区间内最大的覆盖次数
int col[maxn<<2];//当前区间被覆盖的次数
int k,res[100010];
int max(int a,int b){
return a>b?a:b;
}
void pushup(int rt){
sum[rt]=max(sum[rt<<1],sum[rt<<1|1])+col[rt];
}
void update(int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){
col[rt]++;
sum[rt]++;
return ;
}
int m=mid;
if(L<=m) update(L,R,lson);
if(R>m) update(L,R,rson);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){
return sum[rt];
}
int m=mid, ret=0;
if(L<=m) ret=max(ret,query(L,R,lson));
if(R>m) ret=max(ret,query(L,R,rson));
return ret+col[rt];//递归回溯的时候把路径上的col【rt】都加上
}
int main(){
int t,i,j,q,a,b,cases=1,tot;
scanf("%d",&t);
while(t--){
tot=0;
memset(col,0,sizeof(col));
memset(sum,0,sizeof(sum));
scanf("%d%d",&k,&q);
for(i=1;i<=q;i++){
scanf("%d%d",&a,&b);b--;
if(query(a,b,1,1000000,1)<k){
res[++tot]=i;
update(a,b,1,1000000,1);
}
}
printf("Case %d:\n",cases++);
for(i=1;i<=tot;i++){
printf("%d ",res[i]);
}
printf("\n\n");
}
}
| | | )_) )_) )_) )___))___))___)\ )____)____)_____)\\ _____|____|____|____\\\__ ---------\ /--------- ^^^^^ ^^^^^ ^^^^ ^^^^^^^ ^^^^^ ^^^