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)) qlog(max(n,m))个点,也不算太多。
       \\;\\;\\; 至于为什么要让最后一列独自美丽:因为每一次移除,都要从最后一列中挑出一个数,再加进去一个数,其特殊之处,需要我们再开一颗线段树。
       \\;\\;\\; 对于每一行所开的线段树,它们的初始状态即为编号的初始状态,在操作时,我们不会将此序列中的数移除,而仅仅是标记,并在最后加上一个未知的数。我们开一个size数组,存当前节点所代表的区间中有多少个点被移除(找真实位置的时候需要)。
       \\;\\;\\; 我们每次在线段树中查找时,会找到你要查询的第x个数在此序列中的真实位置y(因为标记过的位置即意味着此位置无用)。若是 y < = m − 1 y<=m-1 y<=m1,那么我们要输出的数就在初始状态代表的序列中;若不是,那我们就要用到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列队(蒟蒻的题解)的主要内容,如果未能解决你的问题,请参考以下文章

[jzoj]5478.NOIP2017提高组正式赛列队

noip2013Day2T3-华容道一个蒟蒻的详细题解

NOIP2017游记

NOIP2017 Day2 T3 列队(treap)

计蒜客NOIP2017提高组模拟赛day2-小区划分

计蒜客NOIP2017提高组模拟赛day2-直线的交点