斜率DP總結

chunlvxiong的博客


T1:防御準備

   ?三個月后第一次寫博客,我們從這個題開始:http://www.lydsy.com/JudgeOnline/problem.php?id=3156。

  這道題DP方程比較好寫:用dp[i]表示1到i全部被控制的最小代價,那么dp[i]=min{dp[j]+(i-j)*(i-j-1)/2+a[i]}/*表明j+1到i被i守衛*/

  然后O(N^2)大T特T。

  這里就要用到斜率優化DP,下面給出我推導這道題的過程。

  設i從j轉移比從k轉移要優,那么:

  dp[j]+(i-j)*(i-j-1)/2<dp[k]+(i-k)*(i-k-1)/2
  dp[j]+[i^2-2ij+j^2-i+j]/2<dp[k]+[i^2-2ik+k^2-i+k]/2
  dp[j]-dp[k]<[-2ik+k^2+k+2ij-j^2-j]/2
  dp[j]-dp[k]<[k(k+1)-j(j+1)]/2-(ik-ij)
  i(k-j)<{k(k+1)/2+dp[k]}-{j(j+1)/2+dp[j]}
  j<k:{k(k+1)/2+dp[k]}-{j(j+1)/2+dp[j]}/(k-j)>i-->{j(j+1)/2+dp[j]}-{k(k+1)/2+dp[k]}/(j-k)>i
  j>k:{k(k+1)/2+dp[k]}-{j(j+1)/2+dp[j]}/(k-j)<i-->{j(j+1)/2+dp[j]}-{k(k+1)/2+dp[k]}/(j-k)<i

  由此可以得到一個很像斜率的東西:令yi=i(i+1)/2+dp[i],xi=i,那么你發現這個式子變成了:

  j<k:(yj-yk)/(xj-xk)>i

  j>k:(yj-yk)/(xj-xk)<i

  這個東西維護起來要好很多,因此yj,yk,xj,xk都是不受i的影響的,如果你能把式子化成類似這樣的形式,那么你幾乎已經成功了。

  一般斜率DP使用單調隊列進行維護。(下面描述中,我們用g(a,b)表示(ya-yb)/(xa-xb))

  隊頭維護:本題中優于i是遞增的,用a表示隊列第一項,用b表示隊列第二項(a<b),那么g(b,a)<i時,則以后g(b,a)一定一直小于i,也就是說以后b一定一直優于a,那么可以將a彈出。

  隊尾維護:(這個你也可以畫圖維護一個類似凸包的東西,但我更喜歡直接推)

  用a表示隊尾倒數第二項,用b表示隊尾最后一項,用c表示當前要插入的元素(a<b<c)。

  可以發現當g(a,b)>g(b,c)時,b不可能成為最優解。

  1、當g(a,b)>i,表明a優于b,b不是最優解。

  2、當g(a,b)<i,則g(b,c)<g(a,b)<i,表明b劣于c,b不是最優解。

  因此可以將b彈出。

  轉移的時候直接取出隊頭元素進行轉移即可,整個DP復雜度變為O(N),A掉此題。

  注意點:上述過程中,請重視正負性的問題,這也許會導致不等式變號,從而改變整個式子。

  來個簡單點的題目:http://www.lydsy.com/JudgeOnline/problem.php?id=1597(其實這道才是我的入門題啊!)

貼代碼:(注:可以用乘積式代替g函數)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1000005;
int n,front,rear;
ll a[maxn],dp[maxn],q[maxn];
ll y(ll a){return a*(a+1)/2+dp[a];
}
ll x(ll a){return a;
}
double g(ll a,ll b){return (1.0*(y(a)-y(b)))/(1.0*(x(a)-x(b)));
}
int main(){scanf("%d",&n);for (int i=1;i<=n;i++) scanf("%lld",&a[i]);dp[0]=0;front=rear=0,q[rear++]=0;for (ll i=1;i<=n;i++){while (front<rear-1 && g(q[front+1],q[front])<1.0*i) front++;ll t=q[front]; dp[i]=dp[t]+(i-t)*(i-t-1)/2+a[i];while (front<rear-1 && g(q[rear-2],q[rear-1])>g(q[rear-1],i)) rear--;q[rear++]=i;}printf("%lld\n",dp[n]);return 0;
}

T2:小P的牧場

  題目鏈接:http://www.lydsy.com/JudgeOnline/problem.php?id=3437

  這題比上一題不同之處在于,它到控制它的控制站之間的牧場數目(不包括自身,但包括控制站所在牧場)乘上該牧場的放養量這句話有點礙眼。

  單單使用一個sum前綴和似乎不能表示出DP方程。

  如果i控制j+1到i之間的牧場,那么新的cost就是(i-j-1)*b[j+1]+(i-j-2)*b[j+2]+……+1*b[i-1]+0*b[i]+a[i](a[i]后面忽略不計)

  你發現b[j+1]到b[i]的系數剛好都是-1下去的,所以你考慮維護一個square數組,square[i]=Σb[x]*x|1<=x<=i。

  然后square[i]-square[j]=b[i]*i+b[i-1]*(i-1)+……+b[j+1]*(j+1)。可以用(sum[i]-sum[j])*i-(square[i]-square[j])來表示上面那個式子。

  那么DP方程寫出來了,用dp[i]表示1到i的站被控制的代價,那么dp[i]=min{dp[j]+(sum[i]-sum[j])*i-(square[i]-square[j])+a[i]}

  請仿照上題自行整理成斜率DP的形式,順便總結一下:

  1、首先要化成(yj-yk)/(xj-xk)<a或者>a的形式,其中yi,xi是只跟i有關的式子,為了化成這樣的式子,有時還需要引進前綴和等數組。

  2、隊頭處理:考慮a遞增或是遞減的性質,然后通過g(x,y)與a的關系來判斷是否彈出x。

  3、隊尾處理:類似于幾何上凸包的形狀,盡管我個人直接推導更不容易錯,即g(x,y)<g(y,z)或g(x,y)>g(y,z)的形式。

  4、DP時直接取出隊頭元素計算即可。

  5、千萬注意正負性問題,這是最大的易錯點。(當初之所以搞不懂斜率DP就是因為忽視了正負性的因素)

  類似的一題:http://www.lydsy.com/JudgeOnline/problem.php?id=1096

  再來一題:http://www.lydsy.com/JudgeOnline/problem.php?id=1010

貼本題代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1000005;
int n,front,rear,q[maxn];
ll a[maxn],b[maxn],sum[maxn],square[maxn],dp[maxn];
ll x(ll a){return sum[a];
}
ll y(ll a){return dp[a]+square[a];
}
double g(ll a,ll b){return (1.0*(y(a)-y(b)))/(1.0*(x(a)-x(b)));
}
int main(){scanf("%d",&n);for (int i=1;i<=n;i++) scanf("%lld",&a[i]);square[0]=sum[0]=0;for (ll i=1;i<=n;i++){scanf("%lld",&b[i]);sum[i]=sum[i-1]+b[i],square[i]=square[i-1]+b[i]*i;}dp[0]=0;front=rear=0,q[rear++]=0;for (ll i=1;i<=n;i++){while (front<rear-1 && g(q[front+1],q[front])<1.0*i) front++;int j=q[front]; dp[i]=dp[j]-(square[i]-square[j])+i*(sum[i]-sum[j])+a[i];while (front<rear-1 && g(q[rear-2],q[rear-1])>g(q[rear-1],i)) rear--;q[rear++]=i;}printf("%lld\n",dp[n]);return 0;
}

T3:[Apio2014]序列分割

  題目鏈接:http://www.lydsy.com/JudgeOnline/problem.php?id=3675

  這個題比較復雜,我們慢慢推。

  最暴力的DP:需要三維,分別存儲本次割點,上次割點,分割次數,然后再窮舉上上次割點,進行轉移,復雜度O(N^3K)=T到無邊無際。

  然后第一步非常的巧,我們畫個圖來說明吧:

  

  割裂順序有兩種,得到的價值分別如下:

  1、先割裂sum1和sum2,再割裂sum2和sum3,價值為sum1*(sum2+sum3)+sum2*sum3

  2、先割裂sum2和sum3,再割裂sum1和sum2,價值為sum3*(sum1+sum2)+sum1*sum2

  發現了什么-->兩種割裂方式價值是一樣的-->確定了割裂位置的話,割裂得到價值與割裂順序無關。

  這個結論的好處就是我們可以直接從開頭一刀刀割向結尾,DP方程變為:(用dp[i][c]表示序列前i項已經割好,且割了c次的最大價值)

  利用前綴和優化:dp[i][c]=max{dp[j][c-1]+(sum[i]-sum[j])*sum[j]}

  復雜度是O(N^2K)的,仍然T的厲害,我們必須再去掉一個N,O(NK)才能A掉此題。

  利用上述的斜率優化嘗試一下:

  dp[j][c-1]+(sum[i]-sum[j])*sum[j]>dp[k][c-1]+(sum[i]-sum[k])*sum[k]

  (dp[j][c-1]-sum[j]^2)-(dp[k][c-1]-sum[k]^2)>sum[i]*(sum[k]-sum[j])

  令yi=dp[i][c-1]-sum[i]^2,xi=sum[i]

  j<k:(yj-yk)/(xk-xj)>sum[i]-->(yj-yk)/(xj-xk)<-sum[i]

  j>k:(yj-yk)/(xk-xj)<sum[i]-->(yj-yk)/(xj-xk)>-sum[i]

  后面隊頭隊尾的維護請自行推導,復雜度可以少一個N,O(NK)應該能過去。

  本題空間限制128MB,如果開一個100000*200的long long數組=MLE,因此需要滾動數組。

  但是這題我調了很久,下面來好好說一說關于0的問題(本題(xj-xk)可能為0)

  到(dp[j][c-1]-sum[j]^2)-(dp[k][c-1]-sum[k]^2)>sum[i]*(sum[k]-sum[j])這一步為止,我們只進行加減法,所以這一步的式子是可靠的。

  那么xj-xk=0,所以要判斷yj-yk是否大于0即可,如果yj-yk>0,那么無論sum[i]等于幾,j都優于k。

  好像用乘積式可以解決問題,但是我用乘積式一直WA,所以換了一種更好的解決0問題的方法(對于本題而言),由于本題0的存在毫無意義,在輸入時把ai=0的全部刪去,以保證xj-xk不等于0。

貼代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100005;
int n,k,front,rear,q[maxn];
ll a[maxn],sum[maxn],dp[maxn],Dp[maxn];
ll x(ll a){return sum[a];
}
ll y(ll a){return dp[a]-sum[a]*sum[a];
}
double g(ll a,ll b){return (1.0*(y(a)-y(b)))/(1.0*(x(a)-x(b)));
}
int main(){scanf("%d%d",&n,&k),sum[0]=0;for (int i=1;i<=n;i++){scanf("%lld",&a[i]);if (!a[i]) n--,i--; else sum[i]=sum[i-1]+a[i];}memset(dp,0,sizeof(dp));for (int c=1;c<=k;c++){front=rear=0,q[rear++]=0;for (int i=1;i<=n;i++){while (front<rear-1 && g(q[front+1],q[front])>-sum[i]) front++;int j=q[front]; Dp[i]=dp[j]+(sum[i]-sum[j])*sum[j];while (front<rear-1 && g(q[rear-2],q[rear-1])<g(q[rear-1],i)) rear--;q[rear++]=i;}for (int i=1;i<=n;i++) dp[i]=Dp[i];}printf("%lld\n",Dp[n]);return 0;
}

下面給一道相對簡單的題目:http://www.lydsy.com/JudgeOnline/problem.php?id=1911

轉載于:https://www.cnblogs.com/chunlvxiong/p/8078426.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/251262.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/251262.shtml
英文地址,請注明出處:http://en.pswp.cn/news/251262.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

前端使用react-intl-universal進行國際化

一、國際化 / i18n 目前國際化&#xff0c;就是開發者寫對象&#xff0c;一個key關聯若干語種的翻譯。相比于瀏覽器自帶的翻譯功能&#xff0c;語義更加準確。 “國際化”的簡稱&#xff1a;i18n&#xff08;其來源是英文單詞 internationalization的首末字符i和n&#xff0c;…

守護線程Daemon的理解

1、守護線程伴隨著主線程的銷毀而銷毀&#xff1b; 2、jvm虛擬機中有很多守護線程&#xff0c;隨著main函數的結束而結束&#xff0c;自動回收棧中的內容。 Thread t1 new Thread(){Overridepublic void run() {for (int i 0; i < 10; i) {try {Thread.sleep(1000);} catc…

javascript --- 異步函數的順序進行

假設我們希望某一組異步函數能一次進行,在不使用的任何工具的情況下,可能會編寫出類似下面的代碼: funcs[0](function() {funcs[1](function() {funcs[2](onComplete);}) });// 注:以上代碼運行會出現的一些不方便: // 1.回調太深,不利于閱讀..(100層嵌套...); // 2.不能使用循…

2021前端面試題

基礎知識與素養 JS基本功訓練與思考 程序設計的滲透與應用 業務技巧的積累與訓練 生產力轉換 項目的組織架構 轉換專業人才的全面生產力 什么樣的技術水平決定了你應該學習什么樣的知識與技術&#xff0c;什么樣的知識與技術水平決定了你到什么樣的公司&#xff0c;到什么樣的公…

JS的自定義事件(觀察者模式)

1      var Event {2 on: function (eventName, callback) {3 console.log("eventName:"eventName)4 if (!this.handles) {5 Object.defineProperty(this, "handles", {6 …

glog日志庫使用筆記

日志能方便地診斷程序原因、統計程序運行數據&#xff0c;是大型軟件系統必不可少的組件之一。glog 是google的開源日志系統&#xff0c;相比較log4系列的日志系統&#xff0c;它更加輕巧靈活。 在Github上下載glog&#xff0c;解壓后用CMake生成VS2017工程&#xff08;默認生成…

javascript --- 異步工作流的動態排隊技術

很多情況下,使用async.series和async.paralle存在一個明顯的問題,即: 1.其任務隊列是靜態的,在其調用前,一定要明確任務隊列的數量,一旦明確了任務隊列的數量,就不能改變. 2.倘如要同時并發讀取上千個文件,使用async.paralle明顯不可能(各線程搶資源,根本不夠用),使用async.ser…

java中的內部類總結

內部類不是很好理解&#xff0c;但說白了其實也就是一個類中還包含著另外一個類 如同一個人是由大腦、肢體、器官等身體結果組成&#xff0c;而內部類相當于其中的某個器官之一&#xff0c;例如心臟&#xff1a;它也有自己的屬性和行為&#xff08;血液、跳動&#xff09; 顯然…

elementPlus關閉彈窗,頁面原先滾動條消失

一開始以為是彈窗內容超過一屏引起&#xff0c;改為一屏內也不能解決。 打開控制臺&#xff0c;發現彈窗后自動給body標簽加上了類el-popup-parent–hidden&#xff0c;關閉后也沒去除&#xff0c;因此手動刪除該類。 document.getElementsByTagName(body)[0].className ;

在Windows下如何創建虛擬環境(默認情況下)

很多小伙伴平時在使用Python的時候&#xff0c;有的項目需要使用Python2來進行開發&#xff0c;有的項目則是需要Python3來進行開發。當不清楚怎么分開環境的時候&#xff0c;此時兩個環境開始打架&#xff0c;彼此傻傻分不清楚。虛擬環境作為隔離的利器應運而生&#xff0c;其…

javascript --- 隱藏內部實現(最小暴露原則)

看下面的一個例子: function doSomething(a) {b a doSomethingElse( a * 2 );console.log( b * 3 ); }function doSomethingElse(a) {return a - 1; }var b;doSomething( 2 ) ; // 15上述代碼中的doSomethingElse實際上應該是doSomething的"私有"部分,根據最小暴露…

selenium python 入門-元素定位

環境搭建 安裝教程 http://www.testclass.net/selenium_python/install-selenium/ chrome瀏覽器 還需要下載chrome driver 把下載的chromedriver .exe放到chrome安裝目錄下的Application目錄下和 python所在的安裝目錄下&#xff0c;比如我的目錄是C:\Program Files (x86)\Goog…

ES5程序設計轉ES6 筆記

課程鏈接 1. 立即執行函數 特點&#xff1a;執行結束&#xff0c;立即銷毀&#xff1b;獨立作用域執行符號&#xff08;&#xff09;只能跟在表達式后面&#xff0c;不能放在函數聲明后分號可以寫在前面/后面document為傳入實參&#xff0c;doc為形參 ;(function(doc){...co…

DPDK helloworld 源碼閱讀

在 DPDK Programmers Guides 中的 EAL 一篇中有一個圖可以很清晰地看到一個DPDK的應用程序的大致執行思路&#xff1a; 初始化檢查CPU支持、微架構配置等完成后&#xff0c;執行main()函數。 第一步是 rte_eal_init()&#xff0c;核心初始化和啟動。其中線程使用的是pthread庫&…

javascript --- 作用域和閉包

執行環境: // 定義了變量或函數有權訪問的其他數據,決定了它們各自的行為 // 每個執行環境都有一個變量對象與之對應,執行環境中所定義的所有變量和函數都保存在變量對象中 // 某個執行環境中的所有代碼執行完畢后,該執行環境被銷毀,保存在其中的所有變量和函數定義也隨之銷毀…

異步下載圓形進度條顯示進度

圓形進度條參考鏈接即可&#xff1a;使用css3實現圓形進度條 需求點擊下載后遮罩層顯示下載進度&#xff1a; 1.圓形進度條參考以上鏈接&#xff0c;有點小瑕疵&#xff0c;可更改定位距離實現重合。 2.遮罩層&#xff1a; .lbOverlay{display: none;position: fixed;left: 0;…

javascript基本功

隱式類型轉換 var a {_default: 0,toString: function () {return a._default} } if (a 1 && a 2 && a 3) {console.log(解) } 訪問一個變量的時候進行攔截 var _default 0 Object.defineProperty(window, a, {get() {return _default} }) if (a 1 &am…

深信服筆試,抓兔子

*問題描述&#xff1a;抓兔子n個排成一排的洞&#xff0c;編號為1到n&#xff0c;兔子每天晚上會跳到相鄰的一個洞里&#xff0c;小q每天只能白天檢查其中的一個洞&#xff0c;小q會告訴你每天檢查的洞&#xff0c;分析是否一定能抓到兔子示例&#xff1a;3個洞&#xff0c;第一…

es6 --- 模塊

function foo(){var something cool;var another [1, 2, 3];function doSomething() {console.log( something );}function doAnother() {console.log( another.join( " ! " ) );} } // 是一個不明顯的閉包,doSomething()和doAnother()保持了foo的內部作用域接下來…

Java之遞歸遍歷目錄,修改指定文件的指定內容

EditProperties.java 1 package PropertiesOperation.Edit;2 3 import java.io.File;4 5 /**6 * 替換指定Porpoerties文件中的指定內容7 * 三個參數&#xff1a;8 * filePath&#xff1a;存放properties文件的目錄9 * srcStr&#xff1a;需要替換的字符串 10 * desStr&…