[dfs入門]同學排隊
first:題目
描述
N+Q是班長。在校運動會上,N+Q班要進行隊列表演。N+Q要選出\(2* N\)名同學編隊,每人都被編上一個號,每一個從\(1\)到\(N\)的自然數都被某\(2\)名同學佩戴,現在要求將他們排成一列,使兩個編號為\(1\)的同學中間恰好夾\(1\)名同學,兩個編號為\(2\)的同學中間恰好夾\(2\)名同學,·······,兩個編號為\(N\)的同學中間恰好夾\(N\)名同學,N+Q希望知道這樣的排法能否實現。
輸入
僅包括一行,即要處理的\(N\)。
\(1\le N\le 13\)
輸出
輸出有多少種合法的排列方式
second:分析
直接dfs即可,使用回朔法,設 \(\large f_i\)
為了搜尋方便,我們可以規定每個人搜尋是搭檔的序號比他大,也就是說假設你是第\(i\)號,你要佩戴\(j\),那麼你的搭檔是第\(i+j+1\)號同學,而不是第\(i-j-1\)號同學。
先判斷是否佩戴,如果沒有佩戴,在一一列舉可佩戴的編號。
判斷時既要保證自己沒有佩戴,還要保證自己的搭檔沒有佩戴,即設你是第\(i\)
號,你要佩戴\(j\),那麼你也要保證\(i+j+1\)號同學沒有佩戴。
列舉時因為我們已經規定了搭檔順序,所以列舉到\(2n-dep-1\)即可,因為後面的\(dep+1\)
如果找到一組解滿足要求,則能實現,答案加一,時間複雜度為\(O(n\ !)\)
third:新知
因為這道題資料比較強,我們的dfs會超時,所以我們還需要剪枝。
我們搜尋時一般是先搜小的數,然後再搜大的數,導致後面很難搜尋成功,在一棵子樹中浪費了許多時間。
我們就可以先搜大的數,留下一些小空間,再搜小的數,更容易成功,時間就可以到,在打個表,可以通過此題。
注意一下,這裡的搜尋深度\(dep\)不是從\(1\)到\(n\)了,而是從\(n\)到\(1\),所以如果搜尋深度為\(0\)的話,就代表搜到了。
fourth:注意
1、一定要保證自己和搭檔都沒有佩戴。
2、倒序搜尋更快。
3、及時回溯。
4、列舉時列舉到\(2 n-dep-1\)。
5、\(dep\)為\(0\)時代表找到解。
fifth:補充
大家肯定覺得奇怪,為什麼這道題目既然要打表,還要有這麼強的資料,所以肯定還可以優化。
我們用上面的程式跑一跑,就會發現當\(n\%4=1\)或\(0\)時無解,即\(ans=0\),於是我們就得到了一個猜想,證明如下:
設問題的一個可行解為\(\large a_1,a_2,······,a_n\),其中$ a_i\(為標號為\)i$的數字的位置,這些數字的對應數字的位置應該為
\(a_1+1+1,a_2+2+1,······,a_n+n+1\)
這\(2n\)個整數
\(a_1,a_2,······,a_n,a_1+1+1,a_2+2+1,······,a_n+n+1\)
正是\(1,2,······,2n\),因而
\[\begin{aligned} \sum_{i=1}^na_i+\sum_{i=1}^{n}(a_i+i+1)&=\sum_{i=1}^{2n}i\\ 2(\sum_{i=1}^na_i)+n(n+1)/2+n&=2n(2n+1)/2\\ 2(\sum_{i=1}^na_i)&=(3n^2-n)/2\\ 4(\sum_{i=1}^na_i)&=n(3n-1)\\ \end{aligned} \]可見\(n(3n-1)\)應該是\(4\)的倍數,當\(n\%4=0,1,2,3\)時,\(n(3n-1)\%4=0,2,2,0\),故\(n\%4=1\)或\(2\)時,無解,證畢。
sixth:程式碼
#include<bits/stdc++.h>
using namespace std;
int ans,n;
int a[10001];
void dfs(int dep)
{
if(dep==0)//找到解
{
ans++;
return;
}
for(int i=1;i<=2*n-dep-1;i++)//列舉
{
if(a[i]==0&&a[i+dep+1]==0)//自己和搭檔都沒有佩戴
{
a[i]=dep;
a[i+dep+1]=dep;//則佩戴
dfs(dep-1);
a[i]=0;//回溯
a[i+dep+1]=0;
}
}
}
int main()
{
cin>>n;
if(n%4==1||n%4==2)//特判
{
cout<<0;
return 0;
}
dfs(n);
cout<<ans;
return 0;
}