1631 小鲨鱼在51nod小学(线段树区间修改+单点查询:不用下传lazy的区间修改)

题目描述:

1631 小鲨鱼在51nod小学

鲨鱼巨巨2.0(以下简称小鲨鱼)以优异的成绩考入了51nod小学。并依靠算法方面的特长,在班里担任了许多职务。

每一个职务都有一个起始时间A和结束时间B,意为小鲨鱼在[A, B]时间内,担任了某职务(inclusively)。

现在给定小鲨鱼的职务履历表,你可以高效的给出小鲨鱼在某天担任了哪些职务吗?

p.s. 由于小鲨鱼担任的职务太多,所有任期小于一个自然月的职务都忽略不计。(如1月1日~2月1日为一个自然月,即月份加1)

p.p.s. 输入数据保证小鲨鱼同时不担任超过200种职务。(牛!)

p.p.p.s 输入的日期均为合法日期,范围在2000年01月01日~2999年12月31日。

输入

第一行为一个整数n,代表小鲨鱼担任过N种职务。(1 <= n <= 10^5)
接下来的n行,每一行为七个整数,y0, m0, d0, y1, m1, d1, x。意为在<y0, m0, d0>到<y1, m1, d1>时间内,小鲨鱼担任了职务x。(1 <= x <= 10^9)
给定的时间皆合法,且起始日期小于或等于截止日期。职务x是唯一的。

接下来是一个整数q,代表q次查询。(1 <= q <= 10^4)
接下来的q行,每一行为三个整数<y, m, d>,代表查询的日期。时间皆合法。

输出

每一次查询输出一行结果。
首先输出一个整数n,代表此时小鲨鱼担任的职务数。(n可以为0)
接下来是n个整数,代表小鲨鱼担任的职务。职务列表保持升序。

输入样例

4
2000 01 01    2000 01 01    111
2000 01 02    2001 02 02    222
2000 01 28    2000 02 29    333
2000 01 29    2000 02 28    444
4
2000 01 01
2000 01 02
2000 01 28
2000 02 29

输出样例

0
1 222
2 222 333
2 222 333

思路描述:

        乍一看离线查询,但实际上按时间排序后假如职务1被查询1和查询2占用,到了职务2时,

依旧可能被查询3之前几个查询占用;假设换一种策略,先枚举查询再枚举职务,面临同样的

回溯问题,所以离线查询不可行。

       正解在2000~2999年的每一天上建立线段树,每一次设定职务即在树上作区间修改,每一次查询

即单点查询。区间修改+单点查询的线段树是不用下传懒标记什么的,当修改区间完全覆盖某点时,

修改该点并返回,单点查询时只要将从根节点一直到目标叶子节点上的附加值全部相加即可,本体

附加值换成了一个容器。

      PS:为方便在线段树上进行区间操作,用 (year-2000)*372+(month-1)*31+day的散列

函数将输入时间简化。

代码实现:

#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int N=2e6+100;
int dateHash(int y,int m,int d){
	return (y-2000)*372+(m-1)*31+d;
}
int Left[N],Right[N];
vector<int>dat[N],res;
void build(int p,int l,int r){
	Left[p]=l;
	Right[p]=r;
	dat[p].clear();//利用建树的过程清空 
	if(l==r)return ;
	int mid=(l+r)/2;
	build(2*p,l,mid);
	build(2*p+1,mid+1,r);
}
void change(int p,int l,int r,int v){
	
	if(l<=Left[p]&&r>=Right[p]){  //针对单点查询的区间修改 
		dat[p].push_back(v);
		return ;
	}
	int mid=(Left[p]+Right[p])/2;
	if(l<=mid)change(2*p,l,r,v);
	if(r>mid)change(2*p+1,l,r,v);
}
int query(int p,int x){  //查询要一直搜到叶子节点 
	if(Left[p]==Right[p]){
		res.insert(res.end(),dat[p].begin(),dat[p].end());
		return dat[p].size();
	}
	int mid=(Left[p]+Right[p])/2,val=0;
	if(x<=mid)val=query(2*p,x);
	else val=query(2*p+1,x);
	res.insert(res.end(),dat[p].begin(),dat[p].end());//容器叠加 
	return dat[p].size()+val;//本题的输入确保了同一路径上不同节点相同职务不会重复存 
}
int main(){
	
	
	int n,q,y,m,d,yb,mb,db,x;
	while(cin>>n){
		memset(Left,0,sizeof(Left));
		memset(Right,0,sizeof(Right));
		build(1,1,dateHash(2999,12,31));
		for(int i=1;i<=n;i++){
			cin>>y>>m>>d;
			cin>>yb>>mb>>db>>x;
			if(dateHash(yb,mb,db)-dateHash(y,m,d)<31)continue;
			change(1,dateHash(y,m,d),dateHash(yb,mb,db),x);
		}
		cin>>q;
		while(q--){
			res.clear();
			cin>>y>>m>>d;
			cout<<query(1,dateHash(y,m,d))<<" ";
			sort(res.begin(),res.end());
			for(vector<int>::iterator it=res.begin();it!=res.end();it++){
				cout<<*it<<" ";
			}
			cout<<endl;
		}
		
	}
	return 0;
}

THE END;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值