雙向搜索是對於深度優先搜索的一種優化,它的基本思想是:把\(dfs\)從一端開始改成從兩端開始,從而有效減小搜索狀態算法
若是上面的定義不懂的話,請看下面兩這張圖(設\(n\)是搜索層數):數組
上面這個圖就是普通的\(dfs\)在每次拓展兩個節點的狀況下所產生的的搜索樹的形狀優化
這棵搜索樹,每個節點都表明了一次遞歸調用,咱們很容易能夠看出來它的複雜度是\(O(2^n)\)(滿二叉樹狀況下)(其實減不減1無所謂了)spa
下面這個是雙向搜索所產生的兩棵搜索樹(一顆紅色,一顆藍色)code
咱們在這兩棵搜索樹上分別獲得了\(4\)和\(2\)種結果,如今組合一下(綠色部分),獲得了\(4*2=8\)種結果,和原來的搜索結果同樣blog
那麼分析複雜度:咱們一次搜索的複雜度\(O(2^\frac{n+2}{2})\),兩次搜索就是\(O(2^{\frac{n+4}{2}})\)排序
若是咱們樸素的統計答案,那麼一次搜索會產生\(2^{\frac{n}{2}}\)種結果,兩次則平方,變成了\(2^n\)次統計遞歸
把複雜度相加:總複雜度是\(O(2^{\frac{n+4}{2}}+2^n)\)圖片
Q:怎麼還慢了呢?混蛋!get
A:(沒錯就是慢了)
Q:那你講個P!(握緊小拳頭)
A:(先別打,先別打!我還沒講完呢!)
上面的方法慢了,主要緣由是咱們選擇了「樸素」的統計答案
考慮怎麼優化:咱們統計答案的第一步就是判斷這個組合獲得的最終答案合不合法
那麼,咱們能夠考慮對其中一個答案數組排序,而後以另外一個答案數組爲查找元素進行二分查找
假設找到一個元素,由於在它以前的元素必定小於它,因此在這個元素以前的元素也都合法,這樣咱們就不用一個一個的統計答案了,提升了效率
分析這麼作的時間複雜度:
\(sort()\)快速排序一遍\(O(\frac{n}{2}logn)\)
進行\(\frac{n}{2}\)次二分查找,\(O(\frac{n}{2}logn)\)
統計答案總複雜度:\(O(nlogn)\)
算法總複雜度:\(O(2^{\frac{n+4}{2}}+nlogn)\)
這不就快了嗎
傳送門:https://www.luogu.com.cn/problem/P4799
題目中要求咱們求出在不超過總預算的狀況下,小B去看比賽的方案數
首先,數據範圍\(n\leq 40\),普通的\(dfs\)確定會\(TLE\)的飛起,咱們考慮雙向搜索
令\(Mid=\frac{n}{2}\),咱們第一次從\(1\)搜索到\(Mid\),把答案記錄到序列\(a\)中,第二次從\(Mid+1\)搜索到\(n\)答案記錄到序列\(b\)中
如今統計答案,從\(a\),\(b\)中任選一個序列進行快速排序,(這裏排哪一個應該對時間有影響,可是不大),這裏假設咱們對\(b\)排序
咱們的知足答案的狀態是:\(a_i+b_j<=m\),因此咱們對\(a\)數組\(for\)一遍,在\(b\)數組中二分查找第一個大於\(m-a_i\)的元素
假設第一個大於\(m-a_i\)的元素是\(b_j\),那麼在\(j\)以前的元素必定也符合條件(由於\(b\)序列單調不降),因而\(ans+=j\)
另外,由於咱們搜索時會進行一些特判和剪枝,因此咱們搜索和統計答案的複雜度比上面推出來的式子低,總複雜度也遠遠低於\(O(2^{\frac{n+4}{2}}+nlogn)\)
#include<cstdio> #include<utility> #include<algorithm> typedef long long int ll; const ll maxn=(1<<20)+10;//產生的最大狀態數是2^20 ll n,m,price[45],ida,idb,a[maxn],b[maxn],ans;//不開long long見祖宗 //n場比賽,m預算,price[i]表示第i場收費,ida爲a數組的迭代器,idb是b數組的迭代器,ans統計答案 inline ll read(){ ll x=0,fh=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') fh=-1;ch=getchar(); } while('0'<=ch&&ch<='9'){ x=(x<<3)+(x<<1)+ch-'0';ch=getchar(); } return x*fh; }//瞎寫的快讀 void dfs(ll num,ll u,ll end,ll turn){ //num表示枚舉到第num場比賽,u表示已使用預算,end表示搜到第幾場比賽中止,turn表示這是第幾輪搜索 if(num==end+1){//若是搜到了end+1場比賽,則已經完成搜索,記錄答案 if(turn==1){ a[ida]=u;ida++; }//第一輪記錄到a中,迭代器++ else{ b[idb]=u;idb++; }//第二輪記錄到b中迭代器++ return; } dfs(num+1,u,end,turn);//第num場比賽不看 if(u+price[num]<=m)//剪枝,若是要看第num場比賽,那麼先判斷錢夠不夠,不夠的話看個寂寞,直接跳過搜索 dfs(num+1,u+price[num],end,turn); } int main(){ n=read(),m=read(); for(ll i=1;i<=n;i++) price[i]=read();//快速讀入 ll Mid=(n>>1);//取中點Mid dfs(1,0,Mid,1);//第一次搜索 dfs(Mid+1,0,n,2);//第二次搜索 std::sort(b,b+idb);//對b序列排序 for(ll i=0;i<ida;i++){ ll qwq=std::upper_bound(b,b+idb,m-a[i])-b; //在b中二分查找第一個大於n-a[i]的元素 ans+=qwq;//答案加上該元素的下標,分析如上 } printf("%lld\n",ans);//輸出答案 return 0; }
感謝您的閱讀\(OvO\)