Luogu P6852 Mex|构造

题目链接

题目大意:

有一个 \(0\)\(n\) 的排列,下标从 \(0\) 开始,给出 \(m\) 个限制,为 \(l\)\(r\)\(mex\)\(val\),构造这个排列使得满足限制,或判断无解。

\(1 \le n,m\le 5\times 10^5\)

题目思路:

提供一种码量相对不大的方法,目前不开O2最优解第二页。

首先对于每个限制,都意味着 \([0,val-1]\) 都必须在该区间内,\(val\) 必须不在该区间内。特别的,\(val=0\) 意味着 \(0\) 不在该区间内。

因为若 \(val\) 被限定在区间 \([l,r]\) 内,则 \(val-1\) 亦必须在该区间内(若出现矛盾则无解),所以从后往前,数可放置的位置减少,且除 \(0\) 外,可放置区间是连续的

因此,我们应考虑将数从前往后放置,并维护可放数的位置和不能放置的位置。对于\(0\)的放置,因可放置区间不连续,需要特判。对于 \([1,n]\),枚举可放置位置,若找到一个合法空位,则放置即可。若遇到不可放置区间(显然也是连续的),则整段跳过。

按照上文所述的性质,这样放置不会导致无解(除非原本就无解)。因此,除非无解,这个方法一定可以构造合法方案。

现在可得 \(45\) 分。考虑优化。程序时间使用最多的是查询未使用的位置。使用并查集维护每个位置的状况,每个连通块的最后一个位置为未使用。若某个位置被使用,则将该位置所在的连通块(设为 \(x\))与后面一个位置所在的连通块(设为 \(y\))合并,合并时应使 \(fa_x=fa_y\),即前面的接到后面的。

由于原来每个连通块都是未被使用,合并后每个联通块仍旧有一个位置未被使用。且按照上文的合并方法,因为合并时前面指向后面,因此连通块的根节点一定是该块的最后一个位置,亦即未使用的位置。

优化后,查询未使用的位置时间大大减少,可以通过本题。

上代码

#include<bits/stdc++.h>
using namespace std;
int n,m,pl[500100],pr[500100],ql[500010],qr[500010],z[500100];
int l,r,val,q[500100];int fa[500100];
int getfa(int x)
{
	if (fa[x]==x) return x;
	return fa[x]=getfa(fa[x]);
} 
void hebing(int x,int y)
{
	x=getfa(x);y=getfa(y);
	fa[x]=y;
}
int main()
{
//p数组维护可放位置,q数组维护不可放位置
	scanf("%d%d",&n,&m);
	for (int i=0;i<=n;i++)
	  pr[i]=n,ql[i]=n+1;
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&l,&r,&val);
		ql[val]=min(ql[val],l);qr[val]=max(qr[val],r);
		if (!val) z[l]++,z[r+1]--;
		else
		{
			pl[val-1]=max(pl[val-1],l),pr[val-1]=min(pr[val-1],r);
		}
	}
	for (int i=n-1;i>=0;i--)
	{
		pl[i]=max(pl[i],pl[i+1]);
		pr[i]=min(pr[i],pr[i+1]);
	}//p求后缀最大值和最小值
	for (int i=1;i<=n;i++)
	  fa[i]=i;
	for (int i=0;i<=n;i++)//特判0的情况
	{
		if (i) z[i]+=z[i-1];
		if (pl[0]<=i&&pr[0]>=i&&!z[i]) {q[i]=0;hebing(i,i+1);break;}
		if (i==n) 
		{
			cout<<"-1\n";
		  	return 0;
		}//无法寻找到可放置的空位,即无解。下同
	}
	for (int i=1;i<=n;i++)
	{
		bool ok=false;
		for (int j=pl[i];j<=pr[i];j++) 
		{
      	 j=getfa(j);//直接找到空位
		  if (j>=ql[i]&&j<=qr[i]) {j=qr[i];continue;}//是否进入不合法区间
		  if (j<=pr[i]) {q[j]=i;hebing(j,getfa(j)+1);ok=true;break;}//找到空位
		}  
		if (!ok) 
		{
			cout<<"-1\n";
			return 0;
		}
	}
	for (int i=0;i<=n;i++) cout<<q[i]<<" ";
	return 0; 
}

PS:一开始 \(val\) 不在该区间内这一限制被我忽略了导致WA了好久

posted @ 2020-11-23 16:31  fmj_123  阅读(91)  评论(0编辑  收藏  举报