題意:
給定一個長為 \(n\) 序列 \(a\) ,問是否能分成兩個排列,並輸出方案
(排列:從 \(1—n\) 中選取不同的 \(n\) 個元素組成的序列)
思路:
觀察資料範圍可以猜出,這題 \(O(n)\) 能解決;
因為兩個排列是不重疊的,所以可以考慮分別列舉 \(a_1—a_l\) 是否能組成 \(1—l\) 的排列,\(a_{l+1}—a_n\) 是否能構成排列,那麼就用 \(l_i\) 表示 前\(i\) 個數是否能形成一個排列,\(r_i\) 表示後 \(i\) 個數是否能形成排列,當 \(l_i\) 與 \(r_{i+1}\) 同時為真的時候可行,輸出 \(i\) 和 \(n-i\) 即可;
此題還有一個重要的難題,就是如何判斷是一個排列:
比如判斷前 \(i\) 個數是否為排列
- 必然有前 \(i\) 個數的最大值為 \(i\) ;
- 任意小於 \(i\) 的數出現且只出現一次;
當上面兩條都成立時必然為一個排列,具體實現見程式碼。
儘管以上已經為 \(O(n)\) 的可行演算法,但是為了實現方便,可以加一些優化:
因為要分成兩個排列,所以序列 \(a\) 的最大值 \(maxn\) 必然在一個排列中,\(n-maxn\) 必然存在與另一個排列中,所以一個排列的長度為 \(maxn\),另一個為 \(n-maxn\) ,這說明了最多隻存在兩種方案;
因為最多隻有兩種方案,所以若某一數字出現兩次以上,必然不存在方案。
程式碼:
cin>>t;
while(t--)
{
memset(vis,0,sizeof(vis));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int num=0,maxx=0;
for(int i=1;i<=n;i++)
{
maxx=max(maxx,a[i]);
if(vis[a[i]]) continue;
if(a[i]<=maxx)
vis[a[i]]=1,num++;
if(num==maxx&&num==i) l[i]=true;
}
num=0,maxx=0;
for(int i=n;i>=1;i--)
{
maxx=max(maxx,a[i]);
if(vis[a[i]]) continue;
if(a[i]<=maxx)
vis[a[i]]=1,num++;
if(num==maxx&&num==(n-i+1)) r[i]=true;
}
memset(vis,0,sizeof(vis));
int ans=0;
for(int i=1;i<=n-1;i++)
if(l[i]&&r[i+1]) ans++;
cout<<ans<<endl;
for(int i=1;i<=n-1;i++)
if(l[i]&&r[i+1]) cout<<i<<" "<<n-i<<endl;
}