NOIP 2017 提高组 Day 2列队(蒟蒻的题解)
Posted 。✧* ꧁王者꧂✧*
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NOIP 2017 提高组 Day 2列队(蒟蒻的题解)相关的知识,希望对你有一定的参考价值。
\\;\\;\\;
这道题是几个月前写的了,那时候还是初学线段树,看到这种题的时候简直懵逼。
\\;\\;\\;
题面链接
\\;\\;\\;
我们通过推样例就可得知,当前若有一个人请假,那么整个二维数组改变的只有当前这一行和最后一列。
\\;\\;\\;
如果按最暴力的思路,那我们想的可能会是怎么把当前的代号移到二维数组的最右下端,然后让被影响到的代号归位。但是,这样做显然费时费力,那么,再转念一想,我们可不可以不移除,而是进行标记,具体操作就是不改变二维数组中每个数的原始位置,而是在被移除过的位置标记一下flag,意味着此位置已经没有用了,然后在当前这一行末端接上一个数,在最后一列末端也接上一个数,即为完成更改。
\\;\\;\\;
思路大致如此,接下来,我们需要考虑的就是,要如何去实现这个思路。
\\;\\;\\;
小数据的话,我们可以用二维数组来记录,但起不到什么大作用;再想一下,也许我们可以用map来记录,但是,
9
e
10
9e10
9e10的点的个数先不说,就说map自带的
l
o
g
log
log的复杂度,就能让人搞崩溃。
\\;\\;\\;
既然这不行,那也不行,那么,线段树!!!
\\;\\;\\;
可以想一下,如果用线段树的话,再用上动态开点,空间问题可以很好地避免,而且,q*log(n)的复杂度,似乎也很优。~~
\\;\\;\\;
那就具体来谈一下如何用线段树实现吧。我们可以对每行的前m-1个数字存一个线段树,然后再对最后一列开一个线段树,总共是n+1个线段树。可能屏幕前的你会疑问,这不会炸空间吗!!!但是,动态开点可不是盖得,如果最开始还需要建树的话,那就直接炸了。但是动态开点,每一次只会向某一颗线段树里插入一条链,也就是
l
o
g
(
n
)
log(n)
log(n)个新节点,一共
q
∗
l
o
g
(
m
a
x
(
n
,
m
)
)
q*log(max(n,m))
q∗log(max(n,m))个点,也不算太多。
\\;\\;\\;
至于为什么要让最后一列独自美丽:因为每一次移除,都要从最后一列中挑出一个数,再加进去一个数,其特殊之处,需要我们再开一颗线段树。
\\;\\;\\;
对于每一行所开的线段树,它们的初始状态即为编号的初始状态,在操作时,我们不会将此序列中的数移除,而仅仅是标记,并在最后加上一个未知的数。我们开一个size数组,存当前节点所代表的区间中有多少个点被移除(找真实位置的时候需要)。
\\;\\;\\;
我们每次在线段树中查找时,会找到你要查询的第x个数在此序列中的真实位置y(因为标记过的位置即意味着此位置无用)。若是
y
<
=
m
−
1
y<=m-1
y<=m−1,那么我们要输出的数就在初始状态代表的序列中;若不是,那我们就要用到vector了。vector也要开n+1个,分别对应每一行和最后一列,里面存的是新存进去的数。回到正文,若y>=m,我们就要输出vector中的数了。
总结:本题使用了动态开点以及vector,线段树是为了找位置,vector中存新数。
代码见下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
const int N=1e6;
ll n,m,q,cnt,root[N],x,y,bian,ans;
vector<ll> dui[N];
struct ao
{
ll l,r,ls,rs,size;//size指当前这个点中已经被移除的数的个数
#define l(x) tree[x].l
#define r(x) tree[x].r
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define size(x) tree[x].size
}tree[20*N];
void cofe(ll &p,ll l,ll r,ll os) //对size进行操作,过程需要动态开点来维护
{
if(!p) p=++cnt;
l(p)=l,r(p)=r;
size(p)++;
if(l==r) return;
int mid=l+r>>1;
if(os<=mid) cofe(ls(p),l,mid,os);
else cofe(rs(p),mid+1,r,os);
}
ll cz(ll p,ll l,ll r,ll wei) //查找你需要找的第k个数在整个序列中的最终位置
{
if(l==r) return l;
ll mid=l+r>>1;
ll kong=mid-l+1-size(ls(p));//记录左孩子未被移除的点
if(kong>=wei) return cz(ls(p),l,mid,wei);//向左孩子里找
else return cz(rs(p),mid+1,r,wei-kong);//向右孩子里找
}
ll dty_lie(ll x,ll v) //对最后一列的线段树进行操作
{
ll pos=cz(root[n+1],1,bian,x);//pos指你要找的数在当前序列的真实位置
cofe(root[n+1],1,bian,pos);//对当前找到的数要进行移除
ll sum=pos<=n?pos*m:dui[n+1][pos-n-1];//输出,若真实位置小于等于n,输出pos*m,若pos>n,则要在vector里找
dui[n+1].push_back(v?v:sum);//v有无值代表的是不同操作,若v有值,则代表的当前为某一行的操作,在最后一列中加入移除的值;若v无值,则代表的是对最后一列的某个数进行移除。
return sum;
}
ll dty_han(ll x,ll v) //对每一行的线段树进行操作
{
ll pos=cz(root[x],1,bian,v);//pos指你要找的数在当前序列的真实位置
cofe(root[x],1,bian,pos);//对当前找到的数要进行移除
ll sum=pos<m?(x-1)*m+pos:dui[x][pos-m];
dui[x].push_back(dty_lie(x,sum));//当前的数移除后,要在最后一列中加入当前的这个数
return sum;
}
int main()
{
freopen("phalanx.in","r",stdin);
freopen("phalanx.out","w",stdout);
scanf("%lld%lld%lld",&n,&m,&q);
bian=max(n,m)+q;//每个线段树的总区间有多大
while(q--)
{
scanf("%lld%lld",&x,&y);
if(y==m)//在最后一列的线段树中进行寻找
ans=dty_lie(x,0);
else
ans=dty_han(x,y);//当前的线段树中进行查找
printf("%lld\\n",ans);
}
return 0;
}
以上是关于NOIP 2017 提高组 Day 2列队(蒟蒻的题解)的主要内容,如果未能解决你的问题,请参考以下文章