1. 程式人生 > 實用技巧 >Luogu P6852 Mex|構造

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了好久