之前的程序真的醜……如今已經把碼風改良了;html
去掉了以前那些徹底是行爲藝術的屑優化。數組
向那些被個人naive講解和醜陋程序勸退的盆友們誠懇地道個歉(霧函數
(由於筆者是\(2020\)屆中考生,因此這應該是最後一次大改了,之後就不會有時間了……)優化
建議你們在博客裏食用:傳送門spa
要是PJ組再考這麼難的DP,我就當官把CCF取締了code
開個玩笑。htm
首先,題目給定咱們的這\(n\)我的開始等候的時間是亂的,因此咱們要先按照開始等車的時間把這\(n\)我的排個序,而後再離散化(具體來講就是將等待時間相同的若干我的「合併」成一我的)。blog
在結構體中,用pos
表示這一堆人的等待時間,num
表示這一堆人的人數。(具體過程看代碼)get
設\(f(i,j)\)表示用擺渡車已經載了前\(i\)我的,且搭載了第\(i\)我的(不必定只搭載第\(i\)我的)的那趟擺渡車的發車時間是(\(t_i+j\))的最小等候時間和。(\(t_i\)的意義與題意相同)博客
這裏要注意:\(t_i+j\)除了要知足\(j<m\)(對應上面的引理),同時還要知足\(j<t_{i+1}-t_i\)(即\(t_i+j<t_{i+1}\))
由於若是\(j\geqslant t_{i+1}\),那這趟車就能夠把第\(i+1\)我的也搭上了,顯然違反了\(\mathrm{DP}\)狀態的定義。
(在代碼中,咱們用一個名爲border(i)
的#define
表示了這兩個限制)
對於每一個\(f(i,j)\),枚舉上一趟擺渡車的出發時間。
等等!數據範圍寫着:
\[1 \leqslant t_i \leqslant 4\times10^6 \]
你跟我說枚舉時間?你這最起碼都\(O(nt_i) \sim \mathrm{T}(2\times10^9)\) 的時間複雜度了,怎麼\(\mathrm{AC}\)?
彆着急啊,我還沒說完呢。
其實引理已經告訴咱們,咱們不須要把整個\(t_i\)枚舉完。
由引理可得,對於前\(i-1\)個乘客,每一個乘客能搭載的擺渡車的發車時間只有\(m\)種狀況,因此咱們只須要枚舉這\((i-1)\times m\)種狀況便可。其餘狀況都是廢的,不須要去考慮。
這樣作的枚舉量爲\(O(nm) \sim \mathrm{T}(5 \times 10^4)\),相比以前直接枚舉\(t_i\)的時間複雜度\(\mathrm{T}(4 \times 10^6)\)來說,已經小不少了。
接着,假設前一趟擺渡車已經載了前\(k\)我的,那麼咱們要作的就只有兩件事:
這裏,\(l\)的取值範圍有三個條件:
前兩個條件和前面的border()
同樣,再也不贅述。
第三個條件是\(l\leqslant (t_i+j)-m-t_k\)(即\(t_k+l\leqslant (t_i+j)-m\))
緣由很簡單,若是\(t_k+l> (t_i+j)-m\),那麼兩趟車之間相隔的時間確定就\(<m\),顯然不合題意。
(因此這裏還要再定義一個border2(i)=min( border(i),第三個條件 )
)
在狀態轉移方程中的體現就是:
\[f(i,j)=\min_\limits{0 \leqslant k < i,l\leqslant \mathrm{border2(k)}} \{f(k,l)+col(k+1,i,t_i+j)\}\]
這當中,\(col(k+1,i,t_i+j)\)表示第\(k+1\)個乘客到第\(i\)個乘客等候發車時間爲\(t_i+j\)的那趟擺渡車的時間和,直接用一個for
循環累計便可。
(固然,當\(k=0\)時,\(f(k,l)\)恆爲零,表示這趟車直接把前\(i\)我的所有載完,這時等式右側就直接等於\(col(1,i,t_i+j)\))
算一下上面這個狀態轉移方程的時間複雜度:
綜上所述,這個狀態轉移方程的時間複雜度爲\(O(nm\times nm\times n)=O(n^3m^2)\)。
這時間複雜度……也太可觀了吧
因此咱們須要優化!優化!優化!
咱們來關注一下這個式子:\[col(k+1,i,t_i+j)\]
對於每一個\(i,j\),當\(k\)每增長\(1\)時,\(col\)的值就只會減掉\((t_i+j-t_k)\times num_k\)(\(num_k\)就是上文中提到的,結構體中第\(k\)堆人的人數)。
因此咱們能夠在枚舉每一個\(i\)和\(j\)時,就把\(col(1,i,t_i+j)\)算出來(用一個變量\(val\)存起來)
而後,\(k\)從\(1\)開始枚舉,每當\(k\)在循環一開始等於某個值\(x\)時,\(val\)就減去\((t_i+j-t_x)\times num_x\)。
狀態轉移方程就變爲:\[f(i,j)=\min_\limits{0 \leqslant k < i,l \leqslant \mathrm{border2(k)}} \{f(k,l)+val\}\]
這樣一抽出來,時間複雜度就變成了\(O(nm(n+nm))=O(n^2m+n^2m^2)\)
只保留最高次項後,時間複雜度就降爲了\(O(n^2m^2)\)!這就是\(60\)分的作法!
其實你們有沒有想過,枚舉\(l\)這個操做顯得有些多餘,可不能夠省去呢?(畢竟只是求一個最小值而已,我求完一次就把這個最小值存起來不就好了嗎?)
沒錯,上面的想法是正確的!
咱們開多一個數組\(\mathrm{Min}(i,j)= \min_\limits{j\leqslant \mathrm{border}(i)} \{f(i,j)\}\)
則以前的狀態轉移方程能夠簡化爲:
\[f(i,j)=\min_\limits{0 \leqslant k < i} \{\mathrm{Min}(k,\mathrm{border2}(k))+val\}\]
\(\mathrm{Min}\)能夠在求每一個\(f(k,l)\)的時候順帶維護。
由於這裏只枚舉了\(i,j,k\),因此\(\mathrm{DP}\)的時間複雜度是\(O(n^2m)\)!!!
#include<cstdio> #include<algorithm> using namespace std; const int maxn=502,maxm=102; const int INF=0x7fffffff; #define border2(x) min( border(x),lpos-Mem[x].pos ) //第三個條件是用小於等於號鏈接的,因此不用-1 #define border(x) min( m-1,Mem[x+1].pos-Mem[x].pos-1 ) //由於兩個條件都是用小於號鏈接的,因此在循環中要-1 int f[maxn][maxm]; int Min[maxn][maxm]; int a[maxn]; struct Node{int pos,num;}Mem[maxn];int sz; int col(int l,int r,int pos) { int res=0; for(int i=l;i<=r;i++) res+=(pos-Mem[i].pos)*Mem[i].num; return res; } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); sort(a+1,a+n+1); a[0]=-1; for(int i=1;i<=n;i++) { if( a[i]!=a[i-1] ) Mem[++sz].pos=a[i]; Mem[sz].num++; } Mem[sz+1]=(Node){INF,0}; n=sz; for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) f[i][j]=Min[i][j]=INF; for(int i=1;i<=n;i++) for(int j=0;j<=border(i);j++) { int pos=Mem[i].pos+j,lpos=pos-m; int val=col(1,i,pos); f[i][j]=val; for(int k=1; k<i and Mem[k].pos<=lpos ;k++) { val-=(pos-Mem[k].pos)*Mem[k].num; f[i][j]=min( f[i][j],Min[k][border2(k)]+val ); } Min[i][j]=f[i][j]; if( j>0 ) Min[i][j]=min( Min[i][j],Min[i][j-1] ); } printf("%d",Min[n][m-1]);return 0; }
又是一年過去了,這裏就祝你們\(\mathrm{CSP\ 2019\ J/S}\)認證rp++!
(話說,老子打完此次也要隱退了呢。。。