借教室

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;
}

猜你喜欢

转载自www.cnblogs.com/qswx/p/9492419.html