【NOIP2017】列队

题目链接

测试点1~10

观察到$qle 500$,那我们duck只用暴力维护$500$行和最后一列。

换种说法,对行离散化,每次暴力取出数并且平移数组,时间复杂度和空间复杂度都是$O(nq)$。

测试点11~16

所有$x=1$,那就是只会用到第一行和最后一列。

思考一下,我们每次找到第$k$个数,放到最后面。

我们提取一个数出来,发现它后面的所有数的编号都会减一。

也许我们可以考虑用数据结构维护这些编号?然后每次询问就来一个二分查找,辅助数组存数值。看起来很可做。

初始每个位置上存一,每次用前缀和二分找到一个位置,单点减一,然后序列长度增加,最后这个位置加一。

用树状数组维护。

代码1(80分):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define IL inline
#define RG register
#define _1 first
#define _2 second
using namespace std;
typedef long long LL;
#define RI RG int
#define RL RG LL
const int N=3e5;
const int N1=5e4;
const int Q1=500;

IL void qr(RI &x){
    x=0;    RG char ch=getchar();
    while(!isdigit(ch))    ch=getchar();
    for(;isdigit(ch);ch=getchar())    x=(x<<3)+(x<<1)+ch-'0';
}

IL void qw(RL x,RG char ch){
    RI k=0,s[13];
    if(!x)    s[k=1]=0;
    else for(;x;x/=10)    s[++k]=x%10;
    for(;k;k--)    putchar(s[k]+'0');
    putchar(ch);
}

    int n,m,q;

struct Pr{
    int x,y,d;
}a[N+3];

    int t1[Q1+3];
    
IL int bin1(RI l,RI r,RI x){
    RI mid,ans;
    while(l<=r){
        mid=(l+r)>>1;
        if(x<=t1[mid]){
            r=mid-1;    ans=mid;
        }
        else 
            l=mid+1;
        
    }
    return ans;
    
}
    
    LL p1[Q1+3][N1+3];

IL void sol1(){
    for(RI i=1;i<=q;i++)
        t1[i]=a[i].x;
    sort(t1+1,t1+q+1);
    RI mm=1;
    for(RI i=2;i<=q;i++)
    if(t1[i]!=t1[i-1])
        t1[++mm]=t1[i];
    for(RI i=1;i<=q;i++)
        a[i].x=bin1(1,mm,a[i].x);
    
    for(RI i=1;i<=mm;i++)
        for(RI j=1;j<m;j++)
            p1[i][j]=(LL)(t1[i]-1)*m+j;
    for(RI i=1;i<=n;i++)
        p1[0][i]=(LL)i*m;
    
    for(RI k=1;k<=q;k++){
        RL tmp;
        if(a[k].y==m)
            tmp=p1[0][t1[a[k].x]];
        else 
            tmp=p1[a[k].x][a[k].y];
        qw(tmp,'
');
            
        for(RI i=a[k].y;i<m-1;i++)
            p1[a[k].x][i]=p1[a[k].x][i+1];
        p1[a[k].x][m-1]=p1[0][t1[a[k].x]];
        for(RI i=t1[a[k].x];i<n;i++)
            p1[0][i]=p1[0][i+1];
        p1[0][n]=tmp;
        
    }
    
}

IL bool check2(){
    for(RI i=1;i<=q;i++)
    if(a[i].x!=1)
        return false;
    return true;
}
    
    int k,s2[N*3+3];
    LL t2[N*3+2];
    
IL int lowbit(RI x){return x&(-x);}

IL void mdf(RI p,RI x){
    for(;p<=n+m+q;p+=lowbit(p))
        s2[p]+=x;
}

IL int qry(RI p){
    RI ret=0;
    for(;p;p-=lowbit(p))
        ret+=s2[p];
    return ret;
}

IL int bin2(RI l,RI r,RI x){
    RI mid,ans;
    while(l<=r){
        mid=(l+r)>>1;
        if(x<=qry(mid)){
            r=mid-1;    ans=mid;
        }
        else 
            l=mid+1;
        
    }
    return ans;
    
}

IL void sol2(){
    for(RI i=1;i<=n+m-1;i++)
        mdf(i,1);
    for(RI i=1;i<=m;i++)
        t2[i]=i;
    for(RI i=m+1,j=2;i<=n+m-1;i++,j++)
        t2[i]=(LL)j*m;
    
    k=n+m-1;
    for(RI i=1;i<=q;i++){
        RI pos=bin2(1,k,a[i].y);
        qw(t2[pos],'
');
        mdf(pos,-1);
        mdf(++k,1);
        t2[k]=t2[pos];
        
    }
    
}

IL void sol3(){
    
    
}

int main(){
    qr(n);    qr(m);    qr(q);
    for(int i=1;i<=q;i++){
        qr(a[i].x);    qr(a[i].y);    a[i].d=i;
    }
    
    if(q<=500)
        sol1();
    else 
    if(check2())
        sol2();
    else 
        sol3();

    return 0;

}
View Code

测试点17~20

首先,将最后一列单独拿出来,用代码1中的算法维护。

然后发现,操作可以分为两类:

  1. $y=m$,直接在最后一列上维护;
  2. $y<m$,每行的所有操作放到一起,看起来也是类似的问题。

那么,假如我们把最后一列单独拿走,剩下的第$i$行延长$c_i$格($c_i$表示第$i$行的操作数),然后我们发现可以对所有行以及最后一列分别用树状数组维护。

注意到不能开$n+1$个树状数组来在线做,不然会爆空间。

如果每次单独拿出同一行的操作,发现也不能得到答案,因为存在时间跨度,最后一列的数值会流通,我们无从得知加入每一行的数值是哪些。

但是我们发现,单独拿出来做,虽然得不到数值关系,但是可以得到位置关系。也即,我们可以预处理出对于每行,扩容后的数列里,每次操作对应的位置。

扩容后的数列分为两部分:

  1. 下标小于$m$的,这部分是“原生”数值,可以直接算得。注意会爆int。
  2. 下标大于等于$m$的,设它的下标为$y$,那么它就是在第$y-m+1$次操作该行时加入的。考虑到$c_i$的总和是$q$,我们可以开$n$个vector来存。注意vector的下标是从0开始的,所以$y$下标的询问在vector中的下标是$y-m$。

上面的差不多就是主要内容了。再来理清一下思路:

  1. 依次对于$x$相同的询问,利用树状数组,按照时间顺序预处理出在“扩容数列”里的下标(询问$(x,m)$的标记为操作最后一列然后不参与这部分的处理);
  2. 利用树状数组维护最后一列的流动情况,然后每次按时间顺序对询问分情况处理:答案怎么算,需不需要插入vector等等。

再补充一个细节:预处理的时候,每次记录修改树状数组的位置,每新开一行就按照记录复原修改,用总共$O(q)$的时间来完成每行的初始化。

代码2(100分):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define IL inline
#define RG register
using namespace std;
typedef long long LL;
typedef unsigned int UI;
#define RI RG int
#define RC RG char
#define RL RG LL
#define RU RG UI
const int N=3e5;

    int n,m,q;

struct Qry{
    int x,y,d;
}a[N+3];

IL bool cmp1(Qry A,Qry B){
    return A.x==B.x?A.d<B.d:A.x<B.x;
}

IL bool cmp2(Qry A,Qry B){
    return A.d<B.d;
}

    int lim,s[N*2+3];

IL int lowbit(RI x){
    return x&(-x);
}

IL void mdf(RI p,RI x){
    for(;p<=lim;p+=lowbit(p))
        s[p]+=x;
}

IL int qry(RI p){
    RI ret=0;
    for(;p;p-=lowbit(p))
        ret+=s[p];
    return ret;
    
}

IL int sol(RI x){
    RI l=1,r=lim,ans,mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(x<=qry(mid)){
            r=mid-1;    ans=mid;
        }
        else 
            l=mid+1;
        
    }
    return ans;
    
}

    vector<int>rec;
    vector<LL>t[N+3];
    LL ans[N*2+3];

int main(){
    scanf("%d%d%d",&n,&m,&q);
    for(RI i=1;i<=q;i++){
        scanf("%d%d",&a[i].x,&a[i].y);
        a[i].d=i;
        
    }
    
    sort(a+1,a+q+1,cmp1);
    lim=m+q;
    for(RI i=1;i<m;i++)
        mdf(i,1);
    for(RI i=1,p=m-1,pos;i<=q;i++){
        if(a[i].x!=a[i-1].x){
            for(UI j=0;j<rec.size();j++)
                mdf(rec[j],1);
            while(p>=m){
                mdf(p,-1);    p--;
            }
            rec.clear();
            
        }
        
        if(a[i].y==m){
            a[i].y=-1;
            continue;
            
        }
        pos=sol(a[i].y);
        rec.push_back(pos);
        mdf(pos,-1);
        mdf(++p,1);
        a[i].y=pos;
        
    }
    
    sort(a+1,a+q+1,cmp2);
    memset(s,0,(m+q+1)*sizeof(int));
    lim=n+q;
    for(RI i=1;i<=n;i++){
        mdf(i,1);
        ans[i]=(LL)i*m;
        
    }
    
    RL tmp;
    for(RI i=1,p=n,pos;i<=q;i++)
    if(a[i].y==-1){
        pos=sol(a[i].x);
        mdf(pos,-1);
        
        tmp=ans[pos];
        
        ans[++p]=tmp;
        mdf(p,1);
        printf("%lld
",tmp);
        
    }
    else {
        pos=sol(a[i].x);
        mdf(pos,-1);
        
        if(a[i].y<m)
            tmp=(LL)(a[i].x-1)*m+a[i].y;
        else 
            tmp=t[a[i].x][a[i].y-m];
        t[a[i].x].push_back(ans[pos]);
        
        ans[++p]=tmp;
        mdf(p,1);
        printf("%lld
",tmp);
        
    }

    return 0;

}
View Code
原文地址:https://www.cnblogs.com/Hansue/p/12984380.html