NOIP2017 列隊

前置技能:動態開點線段樹html

題意:node

Sylvia 是一個熱愛學習的女♂孩子。ios

前段時間,Sylvia 參加了學校的軍訓。衆所周知,軍訓的時候須要站方陣。git

Sylvia 所在的方陣中有 n*m 名學生,方陣的行數爲 n ,列數爲 m 。數組

爲了便於管理,教官在訓練開始時,按照從前到後,從左到右的順序給方陣中 的學生從 1 到 n*m 編上了號碼(參見後面的樣例)。即:初始時,第 i 行第 j 列 的學生的編號是 (i1)*m+j 。學習

然而在練習方陣的時候,常常會有學生由於各類各樣的事情須要離隊。在一天 中,一共發生了 qq 件這樣的離隊事件。每一次離隊事件能夠用數對 (x,y) (1≤x≤n, 1≤y≤m)(x,y)(1xn,1ym) 描述,表示第 x 行第 y 列的學生離隊。ui

在有學生離隊後,隊伍中出現了一個空位。爲了隊伍的整齊,教官會依次下達 這樣的兩條指令:spa

  1. 向左看齊。這時第一列保持不動,全部學生向左填補空缺。不難發如今這條 指令以後,空位在第 x 行第 m 列。code

  2. 向前看齊。這時第一行保持不動,全部學生向前填補空缺。不難發如今這條 指令以後,空位在第 n 行第 m 列。htm

教官規定不能有兩個或更多學生同時離隊。即在前一個離隊的學生歸隊以後, 下一個學生才能離隊。所以在每個離隊的學生要歸隊時,隊伍中有且僅有第 n 行 第 m 列一個空位,這時這個學生會天然地填補到這個位置。

由於站方陣真的很無聊,因此 Sylvia 想要計算每一次離隊事件中,離隊的同窗 的編號是多少。

注意:每個同窗的編號不會隨着離隊事件的發生而改變,在發生離隊事件後 方陣中同窗的編號多是亂序的。

 

 

q<=500 50分 模擬

 

只有500組詢問,妥妥的暴力

可是若是開出n*m的數組,空間就會炸

不難發現每一個人的出隊只會影響當前行和最後一

因此只要維護這p行和最後一列的信息,空間複雜度:O(p*m+n)

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

using namespace std;

#define N 501
#define M 50001

typedef long long LL;

LL pos[N][M],last[M];

struct node
{
    int x,y;
}e[N];

int h[N];

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

int main()
{
    int n,m,q;
    read(n); read(m); read(q);
    for(int i=1;i<=q;++i) 
    {
        read(e[i].x);
        read(e[i].y);
        h[i]=e[i].x;
    }
    sort(h+1,h+q+1);
    int tot=unique(h+1,h+q+1)-h-1;
    LL t;
    for(int i=1;i<=tot;++i)
    {
        t=(LL)(h[i]-1)*m;
        for(int j=1;j<=m;++j) pos[i][j]=++t;
    }
    for(int i=1;i<=n;++i) last[i]=last[i-1]+m;
    int nx;
    LL ans;
    for(int i=1;i<=q;++i)
    {
        nx=lower_bound(h+1,h+tot+1,e[i].x)-h;
        if(e[i].y==m) ans=last[h[nx]];
        else ans=pos[nx][e[i].y];
        cout<<ans<<'\n';
        if(e[i].y!=m)
        {
            for(int j=e[i].y;j<m-1;++j) pos[nx][j]=pos[nx][j+1];
            pos[nx][m-1]=last[h[nx]];
        }
        for(int j=h[nx];j<n;++j) last[j]=last[j+1];
        last[n]=ans;
    }
}

  

 

x=1

所有操做只涉及第一行和最後一列

能夠開兩個數組維護第一行和最後一列

整個過程能夠總結爲兩個操做

1.從序列中找出第k個數並刪除

2.把刪除的數叫入到另外一個序列

咱們能夠開兩棵線段樹維護這些信息

#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>

using namespace std;

#define N 300001

typedef long long LL;

int n;

int sum[N<<2];

LL a[N<<1];
int sum2[N<<3];

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

void build(int k,int l,int r)
{
    sum[k]=r-l+1;
    if(l==r)  return; 
    int mid=l+r>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
}

int query(int k,int l,int r,int pos)
{
    if(l==r) return l;
    int mid=l+r>>1;
    if(pos<=sum[k<<1]) return query(k<<1,l,mid,pos);
    return query(k<<1|1,mid+1,r,pos-sum[k<<1]);
}

void change(int k,int l,int r,int pos)
{
    if(l==r)
    {
        sum[k]=0;
        return;
    }
    int mid=l+r>>1;
    if(pos<=mid) change(k<<1,l,mid,pos);
    else change(k<<1|1,mid+1,r,pos);
    sum[k]=sum[k<<1]+sum[k<<1|1];
}

void build2(int k,int l,int r)
{
    if(l==r) 
    {
        if(l<=n) sum2[k]=1;
        return;
    }
    int mid=l+r>>1;
    build2(k<<1,l,mid);
    build2(k<<1|1,mid+1,r);
    sum2[k]=sum2[k<<1]+sum2[k<<1|1];
}

int query2(int k,int l,int r,int pos)
{
    if(l==r) return l;
    int mid=l+r>>1;
    if(pos<=sum2[k<<1]) return query2(k<<1,l,mid,pos);
    return query2(k<<1|1,mid+1,r,pos-sum2[k<<1]);
}

void change2(int k,int l,int r,int pos,int w)
{
    if(l==r)
    {
        sum2[k]=w;
        return;
    }
    int mid=l+r>>1;
    if(pos<=mid) change2(k<<1,l,mid,pos,w);
    else change2(k<<1|1,mid+1,r,pos,w);
    sum2[k]=sum2[k<<1]+sum2[k<<1|1];
}

int main()
{
    int m,q;
    read(n); read(m); read(q);
    build(1,1,m-1);
    int i=1; LL j=m;
    for(;i<=n;j+=m,++i) a[i]=j;
    build2(1,1,n+q);
    int x,y;  LL ans; 
    for(int i=1;i<=q;++i)
    {
        read(x); read(y);
        if(y<=sum[1])
        {
            ans=query(1,1,m-1,y);
            cout<<ans<<'\n';
            change(1,1,m-1,ans);
            change2(1,1,n+q,n+i,1);
            a[n+i]=ans;
        }
        else
        {
            y-=sum[1];
            y=query2(1,1,n+q,y);
            cout<<a[y]<<'\n';
            change2(1,1,n+q,y,0);
            change2(1,1,n+q,n+i,1);
            a[n+i]=a[y];
        }
    }
}

  

 

 

 

100分

100分和80分其實很類似了,思想上沒有太大的變化

想到不少位置都沒有動,因此用動態開點線段樹

另外一個不一樣點是位置數組須要動態維護,因此開個vector存便可

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#define RG register
#define il inline
#define iter iterator
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const int N=300005,M=6000100;
vector<ll>S[N];
int n,m,Q,tot,root[N],ls[M],rs[M],v[M],cnt=0;

inline void Modify(int &rt,int l,int r,int sa){
    if(!rt)rt=++cnt;
    v[rt]++;
    if(l==r)return ;
    int mid=(l+r)>>1;
    if(sa<=mid)Modify(ls[rt],l,mid,sa);
    else Modify(rs[rt],mid+1,r,sa);
}

inline int qry(int rt,int l,int r,int k){
    if(l==r)return l;
    int mid=(l+r)>>1;
    int sum=mid-l+1-v[ls[rt]];
    if(k<=sum)return qry(ls[rt],l,mid,k);
    return qry(rs[rt],mid+1,r,k-sum);
}

inline ll caline(int x){
    int r=qry(root[n+1],1,tot,x);
    Modify(root[n+1],1,tot,r);
    return r<=n?1ll*(r-1)*m+m:S[n+1][r-n-1];
}
inline ll calrow(int x,int y){
    int r=qry(root[x],1,tot,y);
    Modify(root[x],1,tot,r);
    return r<m?1ll*(x-1)*m+r:S[x][r-m];
}

void work()
{
    int x,y;
    ll ret,tmp;
    scanf("%d%d%d",&n,&m,&Q);
    tot=Max(n,m)+Q;
    while(Q--){
        scanf("%d%d",&x,&y);
        if(y==m){
            ret=caline(x);
            S[n+1].push_back(ret);
            printf("%lld\n",ret);
        }
        else{
            ret=calrow(x,y);
            printf("%lld\n",ret);
            S[n+1].push_back(ret);
            tmp=caline(x);
            S[x].push_back(tmp);
        }
    }
}

int main()
{
    freopen("pp.in","r",stdin);
    freopen("pp.out","w",stdout);
    work();
    return 0;
}
相關文章
相關標籤/搜索