• 借教室


    Description

    我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。

    我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

    借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。

    Analysis

    线段树

    分治专题,却看不出分治的解法。在线算法,把每天都当成一个单位,针对每次借教室的情况,修改一段区间,直到出现负区间,很明显的最小值线段树。O(mlogn)

    加上快读之后只得了95分,通过查阅资料(某谷题解),我了解到了线段树标记永久化这个思想:修改后的懒惰标记不spread,直接在update时加上自己的懒惰标记,这样能节省常数时间。这个优化太正确了,我怎么没有想到呢。

    Code

    #include <bits/stdc++.h>
    
    #define MAXN 1000000
    
    int n,m,r[MAXN],num[MAXN]; 
    
    int read(){
            int f=1;
            char ch=getchar();
            while(ch<'0'||ch>'9'){
                    f=(ch=='-')?-1:f;
                    ch=getchar();
            }
            int get=0;
            while(ch>='0'&&ch<='9'){
                    get=(get<<1)+(get<<3)+ch-'0';
                    ch=getchar();
            }
            return get;
    }
    
    class segment_tree{
            private:
                    class leaf{
                            public:
                                    int data,l,r,minus;
                                    #define data(i) tree[i].data
                                    #define l(i) tree[i].l
                                    #define r(i) tree[i].r
                                    #define minus(i) tree[i].minus
                    }tree[4*MAXN];
                    void update(int i){
                            data(i)=std::min(data(i<<1)-minus(i<<1),data(i<<1|1)-minus(i<<1|1));
                    }
            public:
                    void build(int i,int l,int r,int num[]){
                            l(i)=l;
                            r(i)=r;
                            int mid=(l+r)/2;
                            if(l==r)
                                    data(i)=num[mid];
                            else{
                                    build(i<<1,l,mid,num);
                                    build(i<<1|1,mid+1,r,num);
                                    update(i);
                            }
                    }
                    int query(int i,int l,int r){
                            if(l(i)>r||r(i)<l)
                                    return 0x3f3f3f3f;
                            if(l(i)>=l&&r(i)<=r)
                                    return data(i)-minus(i);
                            return std::min(query(i<<1,l,r),query(i<<1|1,l,r))-minus(i);
                    }
                    void change(int i,int l,int r,int d){
                            if(l(i)>r||r(i)<l)
                                    return;
                            if(l(i)>=l&&r(i)<=r){
                                    minus(i)+=d;
                                    return;
                            }
                            change(i<<1,l,r,d);
                            change(i<<1|1,l,r,d);
                            update(i);
                    }
    }tree;
    
    int main(){
    		freopen("classroom.in","r",stdin);
    		freopen("classroom.out","w",stdout);
            n=read();
            m=read();
            for(int i=1;i<=n;i++)
            	r[i]=read();
            tree.build(1,1,n,r);
            for(int i=1;i<=m;i++){
            	int d,s,t;
            	d=read();
            	s=read();
            	t=read();
            	tree.change(1,s,t,d);
            	if(tree.query(1,1,n)<0){
            		std::cout<<"-1"<<std::endl;
            		std::cout<<i<<std::endl;
            		return 0;
    			}
    		}
    		std::cout<<"0"<<std::endl;
            return 0;
    }
    

    二分查找

    这题确实可以二分,离线算法。因为此订单序列越往右无效的可能性越大,一旦出现无效订单,之后订单更是无效,满足二分查找性质。但是,二分查找的复杂度为O(logn),处理的订单总数为(n/2+n/4+..+1)=O(n),那么对每个订单的处理复杂度要不大于O(logn)才行,可以想到线段树,但是那就没意思了。看了一下zjx解题报告,用的是前缀和优化,这个优化我在一本算法书上见过,具体哪本忘了。

    具体优化方法:利用订单区间修改的性质,新建一个数组,以减法为例,将处理的起点附上-d,终点的下一位附上+d,这样此数组的每个点的前缀和即为每个教室的变动值,修改是O(1)的算法,而判断是处理完所有订单后的一次O(n)循环,最大复杂度为O(n+nlog2n)。

    Code

    #include <bits/stdc++.h>
    
    int n,m,ans,sub,room[1000010],d[1000010],s[1000010],t[1000010],treat[1000010];
    
    int read(){
            int f=1;
            char ch=getchar();
            while(ch<'0'||ch>'9'){
                    f=(ch=='-')?-1:f;
                    ch=getchar();
            }
            int get=0;
            while(ch>='0'&&ch<='9'){
                    get=(get<<1)+(get<<3)+ch-'0';
                    ch=getchar();
            }
            return get;
    }
    
    int search(int l,int r){
    	if(l==r-1)
    		return (l==m)?0:r;
    	int mid=(l+r)/2;
    	if(sub<mid)
    		for(int i=sub+1;i<=mid;i++)
    			treat[s[i]]-=d[i],treat[t[i]+1]+=d[i];
    	else 
    		for(int i=mid+1;i<=sub;i++)
    			treat[s[i]]+=d[i],treat[t[i]+1]-=d[i];
    	sub=mid;
    	int sum=0;
    	for(int i=1;i<=n;i++){
    		sum+=treat[i];
    		if(room[i]+sum<0)
    			return search(l,mid);
    	}
    	return search(mid,r);
    }
    
    int main(){
    	freopen("classroom.in","r",stdin);
    	freopen("classroom.out","w",stdout);
    	n=read();
    	m=read();
    	for(int i=1;i<=n;i++)
    		room[i]=read();
    	for(int i=1;i<=m;i++)
    		d[i]=read(),s[i]=read(),t[i]=read();
    	int ans=search(0,m+1);
    	if(!ans)
    		std::cout<<"0"<<std::endl;
    	else
    		std::cout<<"-1"<<std::endl<<ans<<std::endl;
    	return 0;
    }
    
  • 相关阅读:
    解决:微信小程序富文本识别不了空白p标签的方法
    微信小程序
    fastadmin
    微信小程序
    fastadmin使用笔记
    fastadmin使用笔记
    您有新的订单,请注意查收 提示音
    js获取视频第一帧生成图片
    解决JQ WeUI 的 Picker无法动态传值的问题
    微擎 人人商城小程序获取不到用户信息
  • 原文地址:https://www.cnblogs.com/qswx/p/9492419.html
Copyright © 2020-2023  润新知