[关闭]
@peachyang 2017-05-16T04:01:53.000000Z 字数 6332 阅读 970

周末作业20170508

题解


A - Hotel (POJ3667)
题目详情
题目大意:

一个旅店需要经常对房间进行检查,当输入(1,a)时表示需要入住a个连续房间,输出这几个房间最小的房间号,如房间不够时则输出0;(2,a,b)时,表示a->a+b-1个房间需要办理退房

解题思路:

线段树区间合并,加上lazy标记

AC代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int maxn=50005;
int msum[maxn<<2],lsum[maxn<<2],rsum[maxn<<2],lazy[maxn<<2];
//msum表示 这一节点的总的连续空房间数;lsum表示从左端开始连续的空房间数;rsum表示从右端开始的连续空房间数,lazy表示这个节点是否被标记,如被标记则调用时则需要更新。
void build(int l,int r,int rt){// 建树每个节点的连续空房间总数等于这个区间的房间数
    msum[rt]=lsum[rt]=rsum[rt]=r-l+1;
    lazy[rt]=-1;
    if(l==r) return ;
    int m=(r+l)>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
}
void pushdown(int rt,int len){//标志向下传,延迟标志,简单来说就是先标志,然后等到下次询问或者更新时再去更新线段树
    if(lazy[rt]!=-1){
        lazy[rt<<1]=lazy[rt<<1|1]=lazy[rt];
        msum[rt<<1]=rsum[rt<<1]=lsum[rt<<1]=lazy[rt]?0:len-(len>>1);//如果c为1表示入住,全部置0,否则则恢复则以区间长度
        msum[rt<<1|1]=rsum[rt<<1|1]=lsum[rt<<1|1]=lazy[rt]?0:(len>>1);
        lazy[rt]=-1;//表示这一节点已经处理
    }
}
void pushup(int rt,int len){//将左右子区间合并
    lsum[rt]=lsum[rt<<1];
    rsum[rt]=rsum[rt<<1|1];
    if(lsum[rt]==len-(len>>1))//如果此节点从左边开始房间数恰好等于左子树的长度,则再加上右子树从左边开始的连续房间数
        lsum[rt]+=lsum[rt<<1|1];
    if(rsum[rt]==(len>>1))
        rsum[rt]+=rsum[rt<<1];
    msum[rt]=max(lsum[rt<<1|1]+rsum[rt<<1],max(msum[rt<<1],msum[rt<<1|1]));//在左右子树和区间合并中选取最大
}
void update(int L,int R,int c,int l,int r,int rt){
    if(L<=l&&r<=R){
        msum[rt]=lsum[rt]=rsum[rt]=c?0:r-l+1;
        lazy[rt]=c;
        return ;
    }
    pushdown(rt,r-l+1);//先传递标记
    int m=(r+l)>>1;
    if(L<=m) update(L,R,c,l,m,rt<<1);
    if(m<R) update(L,R,c,m+1,r,rt<<1|1);
    pushup(rt,r-l+1);// 再更新合并区间
}
int  query(int w,int l,int r,int rt){
    if(l==r) return l;
    pushdown(rt,r-l+1);
    int m=(r+l)>>1;
    if(msum[rt<<1]>=w) return query(w,l,m,rt<<1);//左子树
    else if(rsum[rt<<1]+lsum[rt<<1|1]>=w) return m-rsum[rt<<1]+1;//合并区间中考虑
    return query(w,m+1,r,rt<<1|1);//考虑右子树
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,n,1);
    while(m--){
        int tp,a,b;
        scanf("%d",&tp);
        if(tp==1){
            scanf("%d",&a);
            if(msum[1]<a) printf("0\n");
            else {//表示肯定能找到
                int q=query(a,1,n,1);
                printf("%d\n",q);
                update(q,q+a-1,1,1,n,1);
            }
        }
        else {
            scanf("%d%d",&a,&b);
            update(a,a+b-1,0,1,n,1);
        }
    }
    return 0;
}

B - A Corrupt Mayor's Performance Art(HDU5023)
题目详情

题目大意:

(p,a,b,c)表示(a,b)上c这个颜色;(q,a,b)表示查询(a,b)区间内共有多少中颜色,从颜色升序输出

解题思路:

因为最多只有30种颜色,所以可以用2的幂分别表示一种颜色,然后区间和与每一位进行与运算,则可以算出有多少种颜色,线段树lazy标记

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>

using namespace std;

const int maxn=1000005;
typedef long long  LL;
LL add[maxn<<2],sum[maxn<<2];//sum表示这个节点区间颜色的和,add表示lazy标记
int ans[35];
char s[5];
void pushup(int rt){//合并左右子树颜色种类
    sum[rt]=sum[rt<<1]|sum[rt<<1|1];
}
void build(int l,int r,int rt){//建树
    add[rt]=0;
    if(l==r){
        sum[rt]=2;//先将每一个初始化为2
        return ;
    }
    int m=(r+l)>>1;
    build(l,m,rt<<1);
    build(m+1,r,rt<<1|1);
    pushup(rt);
}
void pushdown(int rt){
    if(add[rt]){
        add[rt<<1]=add[rt<<1|1]=add[rt];
        sum[rt<<1]=sum[rt<<1|1]=add[rt];
        add[rt]=0;
    }
}
void update(int L,int R,int c,int l,int r,int rt){
    if(L<=l&&r<=R){
        add[rt]=1<<(c-1);//add存贮颜色类别,传递
        sum[rt]=1<<(c-1);
        return ;
    }
    int m=(l+r)>>1;
    pushdown(rt);//先向底层传递
    if(L<=m)  update(L,R,c,l,m,rt<<1);
    if(m<R)  update(L,R,c,m+1,r,rt<<1|1);
    pushup(rt);//再从底层向上更新
}
LL query(int L,int R,int l,int r,int rt){
    LL res=0;
    if(L<=l&&r<=R)
        return sum[rt];
    pushdown(rt);
    int m=(r+l)>>1;
    if(L<=m) res|=query(L,R,l,m,rt<<1);
    if(m<R)  res|=query(L,R,m+1,r,rt<<1|1);
    return res;
}
int main(){
    int n,m,a,b,c,cnt;
    while(~scanf("%d%d",&n,&m)){
        if(n==0&&m==0) break;
        build(1,n,1);
        while(m--){
            scanf("%s",s);
            if(s[0]=='Q'){
                memset(ans,0,sizeof(ans));
                cnt=0;
                scanf("%d%d",&a,&b);
                LL q=query(a,b,1,n,1);
                for(int i=0;i<=30;i++)//一位一位进行位运算
                    if(q&(1<<i)) ans[cnt++]=i+1;
                for(int i=0;i<cnt;i++)
                    printf(i==0?"%d":" %d",ans[i]);
                printf("\n");

            }
            else {
                scanf("%d%d%d",&a,&b,&c);
                update(a,b,c,1,n,1);
            }
        }
    }
    return 0;
}

C - Assignment(HDU 5289)
题目详情
题目大意:

一个连续区间的的最大值和最小值之差小于k,问一个数组这样区间的个数

** 解题思路:**

第一种方法RMQ(适用于重叠区间不会对结果造成影响的(最值,gcd等)),第二种方法单调队列

AC代码:
RMQ

#include <iostream>
#include <cstdio>
#include <cmath>

using namespace std;
int dp1[100005][30],dp2[100005][30],a[100005],n,m;//dp1表示最大值,dp2表示最小值
void init_rmp(){  //一般的RMQ的写法
    for(int i=1;i<=n;i++){
        dp1[i][0]=a[i];
        dp2[i][0]=a[i];
    }
    for(int j=1;(1<<j)<=n;j++){
        for(int i=1;(i+(1<<j)-1)<=n;i++){
            dp1[i][j]=max(dp1[i][j-1],dp1[i+(1<<(j-1))][j-1]);
            dp2[i][j]=min(dp2[i][j-1],dp2[i+(1<<(j-1))][j-1]);
        }
    }
}
int rmp(int l,int r){
    int k=0;
    while(((1<<(k+1)))<=r-l+1) k++;//找到小于等于这个数的2的次幂
    return max(dp1[l][k],dp1[r-(1<<k)+1][k])-min(dp2[l][k],dp2[r-(1<<k)+1][k]);
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",a+i);
        init_rmp();
        long long ans=0;
        int tp=1;
        for(int i=1;i<=n;i++){//先固定这个区间的右端点,再找出使最值小于m的左端点的最小值
            while(rmp(tp,i)>=m&&tp<i) tp++;
            ans+=(i-tp+1);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

单调队列

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <deque>

using namespace std;

typedef long long LL;
deque<LL> q1,q2;
LL a[100005];

int main(){// q1降序,q2升序
    int n,T;
    LL ans,k;
    scanf("%d",&T);
    while(T--){
        scanf("%d%lld",&n,&k);
        for(int i=1;i<=n;i++)
            scanf("%lld",a+i);
        ans=0;
        while(!q1.empty()) q1.pop_back();
        while(!q2.empty()) q2.pop_back();
        int i,j=1;
        for(i=1;i<=n;i++){
            while(!q1.empty()&&q1.back()<a[i])
                q1.pop_back();
            q1.push_back(a[i]);//将比a[i]小的值全部去除,对结果不造成影响
            while(!q2.empty()&&q2.back()>a[i])
                q2.pop_back();
            q2.push_back(a[i]);//将比a[i]大的值全部去除,对结果不造成影响
            while(!q1.empty()&&!q2.empty()&&q1.front()-q2.front()>=k){
                ans+=(i-j);//这里表示当i添加到最值大于m,则说明j到i-1满足条件所以ans+=(i-1-j+1);
                if(q1.front()==a[j]) q1.pop_front();
                if(q2.front()==a[j])  q2.pop_front();
                j++;

            }
        }
        while(j<=n){//此处容易忘记,注意可能并还没有比较完
            ans+=(i-j);
            j++;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

D - GCD HDU 5726
题目详情
题目大意:

求一个区间gcd,并求整个数组等于此gcd的区间个数

解题思路:

一个区间的gcd用RMQ就可以解决,但是求个数就有些麻烦,先固定一个一个左端,再找出等于gcd等于这一点最大右端点(二分,固定左端点往右是非增序列),再求出此左端点与刚才最大右端点+1的端点gcd,再求出一个最大右端点,知道右端点等于n。将左端点固定从1~n则算出所有的gcd的值个数(用map贮存)
AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <cmath>
using namespace std;

map<int ,long long > mp;
int n,m,T,ncase,dp[100005][30],a[100005];
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}
void rmq(){//RMQ
    for(int i=1;i<=n;i++)
        dp[i][0]=a[i];
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            dp[i][j]=gcd(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int findx(int l,int r){
    int k=(int)log2((double)(r-l+1.0));
    return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}
void settable(){
    mp.clear();
    for(int i=1;i<=n;i++){//遍历左端点
        int g=dp[i][0],j=i;
        while(j<=n){
            int l=j,r=n;
            while(l<r){  //二分求其最大右端点
                int mid=(l+r+1)>>1;
                if(findx(i,mid)==g) l=mid;
                else r=mid-1;
            }//得到此gcd最大右端点
            mp[g]+=(l-j+1);
            j=l+1;//将最大右端点右移一位
            g=findx(i,j);//求一新的gcd;
        }
    }
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",a+i);
        rmq();
        settable();
        int x,y;
        scanf("%d",&m);
        printf("Case #%d:\n",++ncase);
        for(int i=0;i<m;i++){
            scanf("%d%d",&x,&y);
            int tp=findx(x,y);
            printf("%d %lld\n",tp,mp[tp]);
        }
    }
    return 0;
}
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注