题目大意:
有一个 (0) 到 (n) 的排列,下标从 (0) 开始,给出 (m) 个限制,为 (l) 到 (r) 的 (mex) 为 (val),构造这个排列使得满足限制,或判断无解。
(1 le n,mle 5 imes 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
";
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
";
return 0;
}
}
for (int i=0;i<=n;i++) cout<<q[i]<<" ";
return 0;
}
PS:一开始 (val) 不在该区间内这一限制被我忽略了导致WA了好久