【NOIP2017 D2T3】 列队

题目链接:各大OJ上都有。

闲话:毫不过分地说这题我做了一年。

前置知识:线段树(权值线段树和动态开点,也就是主席树)

30分做法:直接模拟即可。

code:

#include<iostream>
#include<cstdio>
using namespace std;
 
const int N = 1010;
int n, m, q;
int que[N][N], a[300010];
void init() {
    int i, j;
    for (i = 1; i <= n; i++)
        for (j = 1; j <= m; j++)
            que[i][j] = (i - 1) * m + j;
    return ;
}
 
void inits() {
    int i;
    for (i = 1; i <= m; i++)
        a[i] = i; 
    return ;
}
 
void lefts() {
    int i;
    for (i = 1; i <= m; i++) {
        if (i - 1 == 0) continue;
        else {
            if (a[i - 1] == 0) {
                a[i - 1] = a[i];
                a[i] = 0;
            }
        }
    }
    return ;
}
 
void left(int x) {
    int i;
    for (i = 1; i <= m;i++) {
        if (i - 1 == 0) continue;
        else {
            if (que[x][i - 1] == 0) {
                que[x][i - 1] = que[x][i];
                que[x][i] = 0;
            } 
        }
    }
    return ;
}
 
void front() {
    int i;
    for (i = 1; i <= n; i++) {
        if (i - 1 == 0) continue;
        else {
            if (que[i - 1][m] == 0) {
                que[i - 1][m] = que[i][m];
                que[i][m] = 0;
            }
        }
    }
    return ;
}
 
int main() {
    scanf("%d%d%d", &n, &m, &q);
    int i, x, y;
    if (n == 1) {
        inits();
        for (i = 1; i <= q; i++) {
            scanf("%d%d", &x, &y);
            printf("%d\n", a[y]);
            int temps = a[y];
            a[y] = 0;
            lefts();
            a[m] = temps;
        }
        return 0;
    }
    init();
    for (i = 1; i <= q; i++) {
        scanf("%d%d", &x, &y);
        printf("%d\n", que[x][y]);
        int temp = que[x][y];
        que[x][y] = 0;
        left(x);
        front();
        que[n][m] = temp;
    }
    return 0;
}

50分做法:可以发现我们其实只需要维护q行和最后一列就可以了,所以可以离散化一下,然后再模拟。

code:

#include<iostream>
#include<algorithm>
using namespace std;
 
const int Q=510, Max=5e4+10;
typedef long long ll;
struct question{
    int x, y;
} que[Q];
int n, m, q, tmp[Q];
ll ans, map[Q][Max], lastline[Max];
 
bool cmp(int a, int b) {
    return a<b;
}
 
int main() {
    int tot;
    cin >> n >> m >> q;
    for (int i=1; i<=q; i++) {
        cin >> que[i].x >> que[i].y;
        tmp[i]=que[i].x;
    }
    for (int i=1; i<=n; i++)
        lastline[i]=i*m;
    sort(tmp+1, tmp+q+1, cmp);
    tot=unique(tmp+1, tmp+q+1)-tmp-1;
    for (int i=1; i<=tot; i++) {
        ll k=(ll)(tmp[i]-1)*(ll)m;
        for (int j=1; j<m; j++)
            map[i][j]=++k;
    }
    for (int i=1; i<=q; i++) {
        int newx;
        for (int j=1; j<=tot; j++)
            if (tmp[j]==que[i].x) {
                newx=j;
                break;
            }
        if (que[i].y==m) ans=lastline[tmp[newx]];
        else ans=map[newx][que[i].y];
        cout << ans << endl;
        if (que[i].y!=m) {
            for (int j=que[i].y; j<m-1; j++)
                map[newx][j]=map[newx][j+1];
            map[newx][m-1]=lastline[tmp[newx]];
        }
        for (int j=tmp[newx]; j<n; j++)
            lastline[j]=lastline[j+1];
        lastline[n]=ans;
    }
    return 0;
}

100分做法:同50分做法,我们用n颗线段树来维护前n行的m-1列和另一颗线段树来维护最后一列,这样对于每一个操作,我们只需从第x颗线段树中把排名为y的点删除,放入到最后一列的末端,然后再把最后一列第x行的点放入到第x行末端即可。注意,若是出队的点本来就位于最后一列,那么只需将其删除,放入最后一列末端即可。

由于要开n+1线段树,会发现要MLE,所以我们采用动态开点的方式(一般用多大内存就开多大)。

另外线段树每个节点需要维护的值是该节点所对应的的区间有多少点已经出队,这样子我们每次就可以很方便地查询排名为k的点。

查询时的操作就是主席树中查询区间第k大值的方法。

最后对于删除的点,为了方便再次查询时不至于出错,我们将其放入vector数组中,来进行维护。

具体可以看代码:
 

#include<iostream>
#include<vector>
#define mid (l+r)/2
using namespace std;
 
typedef long long ll;
const int N=3e5+10, Max=1e7+10;
struct point {
    int l ,r, use;
} p[Max];
vector<ll> Tree[N];
int n, m, q;
int cnt, root[N];
int upper_limit;
 
void build(int &rt, int x, int l, int r) {
    if (!rt) rt=++cnt;
    p[rt].use++;
    if (l<r) {
        if (x<=mid) build(p[rt].l, x, l, mid);
        else build(p[rt].r, x, mid+1, r);
    }
    return ;
}
 
int rank(int rt, int k, int l, int r) {
    if (l==r) return l;
    int lsize=mid-l+1-p[p[rt].l].use;
    if (k<=lsize) return rank(p[rt].l, k, l, mid);
    else return rank(p[rt].r, k-lsize, mid+1, r);
}
 
ll update_m(int x) {
    int r=rank(root[n+1], x, 1, upper_limit);
    build(root[n+1], r, 1, upper_limit);
    ll ans;
    if (r>n) ans=Tree[n+1][r-n-1];
    else ans=1LL*r*m;
    return ans;
}
 
ll update_n(int x, int y) {
    int r=rank(root[x], y, 1, upper_limit);
    build(root[x], r, 1, upper_limit);
    ll ans;
    if (r>=m) ans=Tree[x][r-m];
    else ans=1LL*(x-1)*m+r;
    return ans; 
}
 
int main() {
    int x, y;
    cin >> n >> m >> q;
    upper_limit=max(n, m)+q;
    for (int i=1; i<=q; i++) {
        cin >> x >> y;
        if (y==m) {
            ll ans=update_m(x);
            Tree[n+1].push_back(ans);
            cout << ans << endl;
        }
        else {
            ll ans=update_n(x, y);
            Tree[n+1].push_back(ans);
            cout << ans << endl;
            ll tmp=update_m(x);
            Tree[x].push_back(tmp);
        }
    }
    return 0;
} 

PS:对于主席树掌握比较熟练的同学,这道题目还是比较简单的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值