c'est dur.
數字\(dp\)主要求解的是在給定區間\(l,r\)中滿足條件的解的個數這一類問題.我們一般把求區間\(l,r\)轉換成求區間\(0,r\)和\(0,l-1\),再將得到的結果相減就是答案.對於\(不要62\)這個問題,我們可以設\(f[i][j]\)表示位數為\(i\),開頭的數為\(j\)的情況下滿足條件的答案數,但要注意的是一般情況下,區間\(l,r\)是限制也就是說不能包括,但這道題是要包括兩個端點的,也就是說如果我們計算\(0,r\)這個區間實際上是計算\([0,r)\),但是要求的卻是\([0,r]\)所以我們在一開始的時候就給右端點的位數\(+1\)即可.
那麼遞推式也就很好表示了,對於\(f[i][j]\),如果\(j=4\)那麼\(f[i][j]=0\),如果\(j!=4\),\(f[i][j]=\sum_^(f[i-1][k])\)並且要保證(k!=2||j!= 6)&&k!=4
,於是我們列舉\(i,j,k\)即可完成對\(f\)陣列的初始化
對於求解的部分,如果要求解\(355\),那麼我們就需要把這個數一位一位的拆出來才能限制,也就是說我們根據拆出來的數就可以求\(f[1][0]~f[1][9]\),\(f[2][0]~f[2][9]\),\(f[3][0]~f[3][5]\),從而得到答案,如果在求解的過程中遇到拆出來的乙個數是\(4\)就可以直接退出了,因為這意味著以後的列舉的沒乙個數都會含有\(4\)而這是不符合要求的,對於遇到\(62\)也同理
#include using namespace std;
int n,m;
int f[10][10];
void init()
} }}int get(int k)
d[len + 1] = 0;
for(int i = len;i > 0;i--)
if(d[i] == 4 || (d[i] == 2 & d[i + 1] == 6))
break;
} return rhs;
}int main()
return 0;
}
可以參考我寫的琪露諾的部落格
對於洛谷上的這道題,我們設\(sum[k] = \sum_^kc[i]+1\)(可以把s陣列視為\(c\)的字首和),用\(dp[i]\)表示裝好前\(i\)個玩具的最小費用,不是長度,那麼我們可以得到轉移方程:
\[dp[i]=min_j_1j_2i)\),並且決策\(j_2\)優於\(j_1\),那麼就有
\[dp[j_1]+(f[i]-f[j_1]-1-l)^2 \geq dp[j_2]+(f[i]-f[j_2]-1-l)^2\\
拆括號得\\
dp[j_1]+f[i]^2-2f[i](f[j_1]+1+l)+(f[j_1]+l+1)^2\geq dp[j_2]+f[i]^2-2f[i](f[j_2]+1+l)+(f[j_2]+l+1)^2\\
化簡可得:\\
2f[i](f[j_2]+1+l)-2f[i](f[j_1]+1+l) \geq dp[j_2]+(f[j_2]+1+l)^2-(dp[j_1]+f[j_1]+1+l)^2\\
即\\2f[i] \geq \frac\\
令g[i]=(f[i]+l+1)^2,可得:
2f[i] \geq \frac
\]那麼也就是說如果有\(j_1,j_2\)滿足這兩個式子,那麼\(j_2\)一定比\(j_1\)要更優.
那麼為什麼要叫斜率優化呢?其實我們觀察最終得到的式子可以吧\(dp[i]+g[i]\)看做縱座標,\(f[i]\)看做橫座標,那麼這個式子其實就相當於\(k=\fracy}x}\),也就是乙個一次函式的斜率表示式,當這個斜率\(k \leq 2f[i]\)時則\(j_2\)優於\(j_1\).
根據上面推出來的關係我們就可以用最優的決策來更新答案了,**就是上面公式的翻譯,加上乙個單調佇列
#include using namespace std;
long long n,l;
long long sum[500010],f[500010],head,tail,q[50010],dp[500010],g[500010];
double slope(int x,int y)
int main()
g[0] = (l + 1) * (l + 1);
for(int i = 1;i <= n;i++)
printf("%lld\n",dp[n]);
return 0;
}
狀壓\(dp\)是動態規劃的一種,通過將狀態壓縮為整數來達到優化轉移的目的 -- \(oi wiki\) 狀態壓縮的思想是用二進位制來表示狀態.
狀壓\(dp\)的時間複雜度是\(o(n^22^n)\)的,通常只能用於\(n \leq 21\)的資料範圍
我們定義陣列\(dp[i][j]\)表示的是老鼠走到第\(i\)個乳酪,且之前走過的狀態為\(j\)時所用的最短的距離.舉個栗子:\(dp[9][166]\)代表的就是老鼠走到了第\(9\)塊乳酪,之前已經走過第\(2,3,6,8\)塊乳酪,因為\(166\)用二進位制表示就是\(10100110\),值得注意的是,這裡的第\(i\)狀態是從二進位制低位到高位來算的.
對於\(dp\)陣列的初始化:首先先賦值為最大值,可以使用memset(dp,127,sizeof(dp))
,接著如果只經過第\(i\)塊乳酪,那麼對應的\(dp\)陣列的值就是原點到這個乳酪的座標的直線距離.用程式表示就是dp[i][1 << (i - 1)] = dis[0][i]
.為什麼是左移\(i-1\)位呢?我們可以手推一下,假設現在我們只到了第\(1\)塊乳酪,那麼對應的狀態(二進位制)因該是\(0001\),轉換為十進位制就是\(1\),\(1=2^0\),所以我們其實是左移\(0\)位,如果到了第\(2\)塊乳酪,則二進位制下的表示是\(00010\),對應的十進位制就是\(2\),\(2 = 2^1\)所以實際上左移了\(1\)位,這也就是左移\(i-1\)位的原因.
接下來就是轉移方程了,\(dp[i][k] = min(dp[i][k],dp[j][k-2^] + dis[i][j])\),\(dp[j][k-2^]\)表示的是現在老鼠在\(j\)點,並且沒有走過\(i\)點的最短距離,\(dis[i][j]\)就是\(i,j\)之間的距離,為什麼可以表示沒有走過\(i\)的狀態呢?因為\(dp[i][k]\)要求的就是走過了\(i\)的最佳答案,所以\(k\)的二進位制表示中在\(i\)那一位是\(1\),而\(dp[j][k-2^]\)中,\(k\)減去了\(2^\)所以剛好把\(k\)二進位制表示中\(i\)那一位的\(1\)減成了\(0\),至於為什麼是\(2^\)前文有解釋.
關於統計答案,就是找出\(min(dp[i][2^n-1])\)就可以了,因為已經明確了最終的狀態是全部乳酪都要吃,所以對於每乙個\(i\)只要保證描述狀態的那一維也就是\(2^n-1\)也就保證了每乙個乳酪都被吃了,所以每乙個\(i\)都是成立的合法解,所以取最小值就行.再理解一下,為什麼\(2^n-1\)就可以保證代表每乙個乳酪都被取到了呢?根據上文的描述我們知道乳酪\(i\)被取了就意味著在\(dp\)陣列第二維描述狀態的數的二進位制位上第\(i\)位是\(1\),那麼其實我們只要在統計答案的時候保證描述狀態的每一位都是\(1\)即可,那麼我們先假設\(n=3\),那麼對應的最終狀態的二進位制表示因該是\(111\),十進位制下就是\(7\),而\(7=8-1,8=2^3,8\)的二進位制表示是\(1000\),那麼再在\(8\)的二進位制位上\(-1\),就變成\(111\),也就是答案了.
這就是狀壓\(dp\)的基本思路和解法,其實應用過的範圍不是很廣,因為可以支援的資料範圍不大,但是這種用二進位制來優化狀態表示和轉移的方法還是很有學習價值的.
下面就是洛谷上例題的**了:
在實現的時候還是要注意精度,因為要不然的話太小的小數比如\(0.001\)就會被計算成\(0\),導致\(wa\),所以盡量所有含有實數運算的函式和引數都要定義成\(double\)型別
#include using namespace std;
double dis[20][20];
struct pointpos[20];
double dp[20][34000];
int n;
double pow(double x)
double calc_dis(int x,int y)
int main()
pos[0].x = pos[0].y = 0;
for(int i = 0;i <= n;i++)
} for(int i = 1;i <= n;i++)
dp[i][1 << (i - 1)] = dis[0][i];
for(int k = 1;k < (1 << n);k++)
} }for(int i = 1;i <= n;i++)ans = min(ans,dp[i][(1 << n) - 1]);
printf("%.2lf\n",ans);
return 0;
}
dp專題總結
1 做題感覺 大部分時候看到題感覺一頭霧水,在明確告訴這是動態規劃的題時會刻意往這方面想,縮小問題規模。如果沒說的話,可能根本不會朝這方面去想。感覺好難做起來理解起來都很費勁,專題中有很多題是稍微變了一下,就暈了,會在各方面細節出問題。就像登山問題和合唱團問題,感覺他們一模一樣,樣例也通過了,就是過...
dp專題總結
所有的dp關鍵有兩點 1.看出來這是一道dp題 看時間複雜度 2.狀態轉移方程!其中狀態的確立和推出狀態轉移方程是個難點,而且dp題還經常會和其他知識點融合在一起搞你,非常靈活。先從有跡可循的一些經典dp問題入手 一.數字dp 1.確立狀態 如何確立乙個正確的dp陣列?dp pos state1 s...
DP專題 學習筆記 數字 DP
目錄3.練習題 update 2021 2 23 最近作者發現數字 dp 的 f 陣列初始化有問題,導致 出現了根本性錯誤 原理不變 現在已經糾正,對各位讀者造成的困擾深表歉意。數字 dp,是一種 dp 廢話 專門用於數字統計類問題。這種問題首次接觸可能會有些難理解,但是練過幾道題之後就會掌握套路了...