源哥每日一题第十三弹 百练4124:海贼王之伟大航路 状压dp
連接:http://bailian.openjudge.cn/practice/4124
題意:從1到n走過所有點恰好一次最短時間。亂搞的話會完美的超時(階乘級別的復雜度,雖然范圍很小,但是也足夠超時了)。
思路:先想一個不太成熟的思路。用dp[j][s]表示。s記錄的是每個點是否被走過的狀態。而dp[s][j]表示的是從1走到j狀態所用的最小時間。這樣的思路成不成立呢?首先,考慮初始值。開始是在1號點,那么dp[1][1]自然就是0了,其他就是max;另外,題面說只要遍歷每一個點,而于順序的話,并沒有要求,也就是說,通過任何一種演變方式到達該狀態的方式都是等價的,滿足了動態規劃無后效性的要求。同時,對于每一個時刻,當前位置存儲的值都是當前最優解,既問題具有最優子結構性質。同時,對于每一個演變,我們可以在dp[s][j]的基礎上,推出當前狀態的值可以通過上一步演變就到達的狀態進行更新,這也就是所謂“人人為我”的過程。dp方程也好想,既:
dp[i][k] = min(dp[i][k],dp[i_pre][k_pre]+G[i_pre][i]);
接下來就是比較重要的問題了:如何表示這些狀態,以及進行狀態之間的計算呢?
用16個bool?太麻煩了!換一種思路:因為只有16個數,將他們編成二進制編碼101010101010100……每一位代表當前位置所代表的點是否被走過,這樣的話,只需要2^16個無符號shortint(實際用int)就可以表示所有可能的狀態啦。
第一個問題是解決了,可如何進行狀態間的變換呢?請把c語言程序設計翻到xxx頁,有關位運算的章節:
&, 這個東西叫按位與,既每一位依次比較一樣就是1,不一樣就是0。平時常用的判斷奇偶性的n&1就是最簡單的應用。
|,這東西叫按位或,鍵位有點怪,一般在enter附近,意思是每一位依次比較有1就是1,全0就是0
^,按位異或,也是中文輸入法下省略號的打法。官方的話是相同為0,不同為1,我的理解就是不帶進位的加法。
~,取反,在tab上面,int下的話就是~x = -x-1 最常用的那個while(~scanf)用的就是這個原理(~0 = -0-1 = -1 = EOF)。
<< >> 左移右移 不多說,乘2除2
?
一些比較清奇的用法
從低位到高位,取n的第m位
return (n >> (m-1)) & 1;
從低位到高位.將n的第m位置1
return n | (1 << (m-1));
從低位到高位,將n的第m位置0
return n & ~(1 << (m-1));
(x&-x)只保留最低位的1
具體用法的話讀代碼,體會一下:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm>using namespace std;void read(){ #ifndef ONLINE_JUDGEfreopen("D:\\fengyu\\Jiang_C\\.vscode\\in.txt","r",stdin);freopen("D:\\fengyu\\Jiang_C\\.vscode\\out.txt","w",stdout); #endif } int G[20][20]; int dp[20][(1<<16)+5]; int main() { read();int n;while (cin >> n) {memset(dp,0x3f,sizeof(dp));for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) {cin >> G[i][j];}}dp[1][1] = 0;int ans = (1<<n)-1;for (int k = 1; k <= ans; k++) {for (int i = 1,_i = 1; i <= n; i++,_i<<=1) {if (k&_i)for (int j = 1, _j = 1; j <= n; j++,_j<<=1) {if (i!=j && k&_j)dp[i][k] = min(dp[i][k],dp[j][k^_i]+G[j][i]);}}}cout << dp[n][ans] << endl;}return 0; }?
轉載于:https://www.cnblogs.com/fengyuzhicheng/p/9153254.html
總結
以上是生活随笔為你收集整理的源哥每日一题第十三弹 百练4124:海贼王之伟大航路 状压dp的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: E:VUE 插件 开发与使用 (一)
- 下一篇: WSL安装xfce4