zoukankan      html  css  js  c++  java
  • Luogu P6852 Mex|构造

    题目链接

    题目大意:

    有一个 (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了好久

  • 相关阅读:
    链表详解自带代码
    队列
    单词翻转
    表达式求值
    一元多项式
    循环链表
    学生成绩管理系统
    双向循环链表
    双向链表
    静态链表
  • 原文地址:https://www.cnblogs.com/fmj123/p/14025230.html
Copyright © 2011-2022 走看看