『动态开点线段数』「NOIP2017Day2T3」列队·毒瘤

题目描述

在这里插入图片描述

题解

观察到每一个点离队以后方阵的变化,可以发现:

  • 对于每一个离队,如果是最后一列,变化的仅仅只有最后一列。
  • 如果离队的不是最后一列,则变化的是这一行以及最后一列。

因此,我们需要动态维护 n n n行列为 [ 1 , m − 1 ] [1,m-1] [1,m1]的方阵队形,并且维护最后一列的方阵队形。

我们所进行的操作就是,在删除节点的位置标记一个 1 1 1 ,在其余位置标记一个 0 0 0 ;那么只需要找到 p p p 0 0 0 就说明找到了第 p p p 个人.这显然可以用线段数进行维护。由于需要建立 n n n 个线段树,所以需要使用动态开店线段数来进行维护。具体如何维护这里就不多说了。

大体的思路知道了,我们来考虑一下如何求解最终需要的答案。

如果我们求解的是最后一列的答案,可以进行这样的操作:

  • 如果当前位置 p o s ≤ n pos\leq n posn ,可以直接输出 m ∗ p o s m*pos mpos. 将这个点边权加上 1 1 1 ,标记未删除。再将这个点添加到一个 v e c t o r vector vector 内,表示这一列后面新加入的节点。
  • 直接输出 v e c t o r vector vector内下标为 n − p o s − 1 n-pos-1 npos1 的数值,进行和上面相同的操作。

如果不是最后一列的答案,处理就变得十分麻烦了:

  • 首先需要找到这一个序列中位置为 y y y 的编号,输出这个编号 t t t 。考虑新加入序列的编号是多少。
  • 在最后一列找到位置为 x x x 的编号,加入原来维护的第 x x x 行中,再将这个数删除加入新数 t t t

代码如下:

#include <bits/stdc++.h>

#define int long long 

using namespace std;
const int N = 5e5;
const int Max = 1e6;

int n, m, q, cnt = 0;
int root[N];

vector <int>  New[N];

struct SegmentTree {
    int sum, lc, rc;
} a[N*10];

void change(int &p,int l,int r,int x)
{
    if (p == 0) p = ++cnt; 
    if (l == r) { a[p].sum ++; return; }
    int mid = l+r >> 1;
    if (x <= mid) change(a[p].lc,l,mid,x);
    if (x > mid) change(a[p].rc,mid+1,r,x);
    a[p].sum = a[a[p].lc].sum+a[a[p].rc].sum;
    return;
}

int ask(int p,int l,int r,int k)
{
    int mid = l+r >> 1;
    if (l == r) return r;
    int sum = (mid-l+1)-a[a[p].lc].sum;//a数组内的sum表示删除的个数 
    //sum表示左边还剩下几个数 
    if (sum >= k)
        return ask(a[p].lc,l,mid,k);
    else return ask(a[p].rc,mid+1,r,k-sum);
}

int query2(int x,int num)
{
    int pos = ask(root[0],1,Max,x), ans;
    change(root[0],1,Max,pos);
    if (pos <= n) ans = m*pos;
    else ans = New[0][pos-n-1];//ans表示最后一列第x行的编号 
    if (num == 0) New[0].push_back(ans);//第x行的转移到了最有一行 
    else New[0].push_back(num);//中间的跳到最有一行 
    //del记录线段数中那些多出来的节点 
    return ans;
}

int query1(int x,int y)
{
    int pos = ask(root[x],1,Max,y), ans;
    change(root[x],1,Max,pos);
    if (pos < m) ans = (x-1)*m+pos;//原来存在 
    else ans = New[x][pos-m];//新增加 
    New[x].push_back(query2(x,ans)); 
    return ans;
}

signed main(void)
{
    scanf("%lld %lld %lld", &n, &m, &q);
    for (int i=0;i<=n;++i) root[i] = ++cnt;
    while (q --)
    {
        int x, y;
        scanf("%lld %lld", &x, &y);
        if (y == m) printf("%lld\n", query2(x,0));
        else printf("%lld\n", query1(x,y)); 
    }
    return 0; 
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值