NOIP2017 D2T2 寶藏
阿新 • • 發佈:2018-08-18
color 位運算 數據 ring 解法 方法 元素 div pri
NOIP2017 D2T2 寶藏
洛谷題目鏈接:https://www.luogu.org/problemnew/show/P3959
其實就是一道暴力搜索題……只是需要一個狀態壓縮的剪枝比較難想而已
這根本不叫dfs!只是一個遞歸而已……開始就被dfs坑了
思路:
首先一個基本的預處理
數據範圍n≤12,m≤5000,說明肯定有很多沒用的邊,在讀入的時候預處理掉就可以了,另外n很小可以用鄰接矩陣存圖訪問快
解法一:暴力搜索
直接暴力搜索,開一個數組記錄一個點有沒有被訪問過。每到一個狀態,從訪問過的點到未訪問過的點引邊算代價,遞歸枚舉一下即可。但此方法時間復雜度較大,大約為O(n^n)?(沒有仔細算),n<=8差不多能過
期望得分:70分(然而實際得分只有60分)
解法二:狀態壓縮剪枝
解法一很顯然有很多無用的操作,那麽如何進行剪枝呢?
答案是:狀態壓縮(這也是博主第一次接觸到這個)
狀態壓縮就是用一個01串表示一個狀態
每一位0代表未選擇,1代表被選擇
實際操作中我們通常用二進制數來表示狀態壓縮,為了方便我們用倒序(最右邊是第一個,緊挨著是第二個,以此類推)
比如01串010001就可以表示第1個、第5個元素被選擇了
然後使用位運算加速
選擇某個元素(加入狀態):x=x|(1<<(i-1))
判斷第i個元素有沒有選擇:x&(1<<(i-1))(真:被選擇;假:未選擇)
那麽,開一個數組f[i]表示當前根節點下狀態為i的已搜索到的最小代價
這樣我們在搜索時,如果這一次更新的代價比當前狀態下以搜索到的最小代價要大(更新的代價>f[i]),說明這次搜索一定不是最優解,就不用進行遞歸算到最後
剪枝到底能減掉多少我不知道……但這一題可以AC了
期望得分:100分
AC代碼:
1 #include<cstdio> 2 #include<climits> 3 #include<cstring> 4 using namespace std; 5 int f[10000];//狀態壓縮答案存儲 6 int tu[13][13];//鄰接矩陣存圖 7 int n,m;//點、邊數 8 int depth[13];//每個點深度 9 void find(int x) 10 { 11 for(int i=1;i<=n;i++) 12 if(x&(1<<(i-1)))//編號為i的點訪問過,從它開始更新 13 for(int j=1;j<=n;j++) 14 if((!(x&(1<<(j-1))))&&tu[i][j]!=INT_MAX)//編號為j的點未訪問過且i,j間有邊相連 15 if(f[x]+depth[i]*tu[i][j]<f[x|(1<<(j-1))])//連i,j後答案比之前找出答案小則繼續尋找,否則一定不是最優解,不用遞歸 16 { 17 f[x|(1<<(j-1))]=f[x]+depth[i]*tu[i][j]; 18 depth[j]=depth[i]+1; 19 find(x|(1<<(j-1))); 20 } 21 } 22 int min(int a,int b){return a<b?a:b;} 23 int main() 24 { 25 for(int i=1;i<=12;i++) 26 for(int j=1;j<=12;j++) 27 tu[i][j]=INT_MAX; 28 scanf("%d%d",&n,&m); 29 for(int i=1;i<=m;i++) 30 { 31 int u,v,w; 32 scanf("%d%d%d",&u,&v,&w); 33 tu[u][v]=tu[v][u]=min(tu[u][v],w);//存最小邊 34 } 35 int ans=INT_MAX;//保存答案 36 for(int i=1;i<=n;i++)//對每個點暴力枚舉 37 { 38 for(int j=1;j<=(1<<n)-1;j++) 39 f[j]=INT_MAX;//每一個根節點都要初始化f[] 40 f[1<<(i-1)]=0; 41 depth[i]=1;//根節點深度為1 42 find(1<<(i-1)); 43 ans=min(ans,f[(1<<n)-1]); 44 } 45 printf("%d\n",ans); 46 return 0; 47 }
NOIP2017 D2T2 寶藏