決策單調性&wqs二分
其實是一個還算 trivial 的知識點吧……早在 2019 年我就接觸過了,然鵝當時由於沒認真學並沒有把自己學懂,故今復學之(
1. 決策單調性
引入:在求解 DP 問題的過程中我們常常遇到這樣的問題:我們列出了一個 \(dp\) 狀態轉移方程式形如 \(dp_i=\min\limits_{j<i}dp_j+w(j+1,i)\) 或類似的形式,暴力轉移時間複雜度 \(\mathcal O(n^2)\) 過不去,但是你發現這裡的代價函式 \(w(l,r)\) 有一些比較好的性質,譬如單調性或凹凸性等,此時你可以考慮使用決策單調性優化 \(dp\)。
決策單調性,說白了就是假設 \(dp_i\)
那麼問題就來了,如果僅僅通過打表找規律的方法列出決策點那顯然費時費力,有沒有不失去一般性和快捷性的判定方法呢?
注:下文中若無特殊說明,預設為最小化問題,若為最大化問題一般都要將不等號方向反過來。
判定決策單調性的強有力工具:四邊形不等式
四邊形不等式,說白了就是如果對於任意四個滿足 \(a\le b\le c\le d\) 的位置 \(a,b,c,d\),都有 \(w(a,c)+w(b,d)\le w(a,d)+w(b,c)\)(當然如果是最大化問題則要將不等號方向反過來,可以簡單記為“相交優於包含”),那麼對應的 \(dp\) 陣列則滿足決策單調性。
證明:考慮反證法,假設存在某個 \(y<x\) 滿足 \(from_x<from_y\)。方便起見下文中令 \(fx=from_x,fy=from_y\)。
那麼根據 \(from\) 陣列的定義顯然有 \(dp_{fx}+w(fx+1,x)<dp_{fy}+w(fy+1,x)\ \ \ \ \ (1)\)
由於 \(fx+1<fy+1\le y<x\),根據四邊形不等式有 \(w(fx+1,y)+w(fy+1,x)\le w(fx+1,x)+w(fy+1,y)\ \ \ \ (2)\)
\((1)+(2)\),整理得 \(dp_{fx}+w(fx+1,y)<dp_{fy}+w(fy+1,y)\),顯然與最優決策點的定義相悖。
區間包含單調
對於任意 \(a\le b\le c\le d\),都有 \(w(b,c)\le w(a,d)\),則稱 \(w\) 包含單調。
包含單調似乎不能直接用來證決策單調性,因此要配合一些東西食用。
一些推論
沒錯這東西還有一些推論。
推論 \(1\):如果 \(w\) 函式滿足 \(w(l+1,r)+w(l,r-1)\le w(l,r)+w(l+1,r-1)\),那麼 \(w\) 滿足四邊形不等式。
證明:將式子變形得到 \(w(l,r)-w(l,r-1)\ge w(l+1,r)-w(l+1,r-1)\)
推廣可得 \(w(l,r)-w(l,r-1)\ge w(l+k,r)-w(l+k,r-1),k\in\mathbb{Z}^+,l+k\le r-1\)
移項可得 \(w(l,r)-w(l+k,r)\ge w(l,r-1)-w(l+k,r-1)\)
進一步推廣可得 \(w(l,r)-w(l+k,r)\ge w(l,r-p)-w(l+k,r-p),p\in\mathbb{Z}^+,l+k\le r-p\)
移項可得 \(w(l,r-p)+w(l+k,r)\le w(l,r)+w(l+k,r-p)\)
記 \(a=l,b=l+k,c=r-p,d=r\),那麼上式即可寫作 \(w(a,c)+w(b,d)\le w(a,d)+w(b,c)\)
推論 \(2\):如果某個 \(dp\) 方程滿足 \(dp_{i,j}=\min\limits_{k}(dp_{i,k}+dp_{k+1,j}+w(i,j))\),且 \(w(i,j)\) 滿足包含單調和四邊形不等式,則 \(dp\) 陣列也滿足四邊形不等式。
證明:對區間長度歸納,顯然 \(a=b=c=d\) 時必然有 \(dp_{a,c}+dp_{b,d}\le dp_{a,d}+dp_{b,c}\)。
我們要證明對於某個 \(a\le b\le c\le d\),其中 \(a\ne d\),有 \(dp_{a,c}+dp_{b,d}\le dp_{a,d}+dp_{b,c}\),考慮分情況討論:
\(b=c\),那麼設 \(x\) 為 \(dp_{a,d}\) 最優決策點,不妨設 \(x\le b\),對於另外一半映象一下即可,那麼
\[\begin{aligned} &dp_{a,d}+dp_{b,c}\\ =&dp_{a,x}+dp_{x+1,d}+w(a,d)\\ \ge&dp_{a,x}+dp_{x+1,b}+dp_{b,d}+w(a,d)\\ \ge&dp_{a,x}+dp_{x+1,b}+dp_{b,d}+w(a,b)\\ \ge&dp_{a,b}+dp_{b,d} \end{aligned} \]\(b\ne c\),設 \(x\) 為 \(dp_{a,d}\) 的最優決策點,\(y\) 為 \(dp_{b,c}\) 的最優決策點,不妨設 \(x\le y\),那麼
\[\begin{aligned} &dp_{a,d}+dp_{b,c}\\ =&dp_{a,x}+dp_{x+1,d}+w(a,d)+dp_{b,y}+dp_{y+1,c}+w(b,c)\\ =&dp_{a,x}+dp_{b,y}+(dp_{x+1,d}+dp_{y+1,c})+(w(a,d)+w(b,c))\\ \ge&dp_{a,x}+dp_{b,y}+(dp_{x+1,c}+dp_{y+1,d})+(w(a,d)+w(b,c))&(\text{對}x+1,y+1,c,d\text{進行四邊形不等式})\\ \ge&dp_{a,x}+dp_{b,y}+(dp_{x+1,c}+dp_{y+1,d})+(w(a,c)+w(b,d))\\ \ge&=dp_{a,c}+dp_{b,d} \end{aligned} \]得證。
具體應用是可以優化區間 \(dp\),有興趣的可以自己去實現,反正我是沒興趣咯(大霧。
決策單調性的實現
講了一車理論(fei)知識(hua),接下來談談決策單調性怎麼實現。
I. 單峰+1D:單指標跳躍
如果一個 \(dp\) 轉移方程滿足對於所有 \(dp_i\) 都有在 \(from_i\) 左邊的位置對 \(dp_i\) 的貢獻隨下標的增大而單調遞減,在 \(from_i\) 右邊的位置對 \(dp_i\) 的貢獻隨著下標的增大而增大,那麼結合決策單調性可以用一個指標維護決策點,每次求解某個 \(dp_i\) 就指標不斷向後跳直到下一個位置的貢獻劣於當前位置為止。
時間複雜度線性。
例題?抱歉,由於這種情況應用不是太廣泛,因此沒有例題……
II. 每一層之間的 \(dp\) 沒有影響:分治
這種情況的適用範圍為:二維 \(dp\),只有上一層向下一層轉移,同一層之間不會轉移。而且每一層中有決策單調性。
具體來說我們定義一個函式 solve(l,r,pl,pr)
表示當前處理區間 \([l,r]\),該區間中所有位置的決策點都在 \([pl,pr]\) 中,記 \(mid=\lfloor\dfrac{l+r}{2}\rfloor\),每次我們暴力列舉 \([pl,pr]\) 中的決策點更新 \(dp_{mid}\) 即可,我們假設決策點為 \(pos\),那麼我們繼續執行 solve(l,mid-1,pl,pos),solve(mid+1,r,pos,pr)
即可,正確性顯然,由於遞迴層數最多 \(\log n\),每層跳躍長度總和 \(\mathcal O(n)\),因此總複雜度 \(\mathcal O(n\log n)\)。
有時候我們還會遇到這樣的問題:代價函式 \(w(l,r)\) 不好直接求,但利用類似於莫隊這種移指標的方式可以求得,譬如區間數顏色,此時也可以通過這種方式計算代價函式,由於每次移動左右端點都在候選區間中移,因此指標的移動次數也是 \(\mathcal O(n\log n)\) 的。
例題:
1. CF868F Yet Another Minimization Problem
模板題,記 \(w(l,r)\) 表示 \([l,r]\) 中相同數字的對數,那麼顯然有 \(w(l,r-1)+w(l+1,r)\le w(l,r)+w(l+1,r-1)\),且等號不成立當且僅當 \(a_l=a_r\),因此該 \(dp\) 是滿足決策單調性的,又因為同一層之間的 \(dp\) 互不影響,因此可以使用分治優化,複雜度 \(n\log n\)。
const int MAXN=1e5;
const int MAXK=20;
int n,k,a[MAXN+5];ll dp[MAXN+5][MAXK+3];
int cl=1,cr=0,buc[MAXN+5];ll sum=0;
void push(int x){sum-=1ll*buc[a[x]]*(buc[a[x]]-1)>>1;buc[a[x]]++;sum+=1ll*buc[a[x]]*(buc[a[x]]-1)>>1;}
void pop(int x){sum-=1ll*buc[a[x]]*(buc[a[x]]-1)>>1;buc[a[x]]--;sum+=1ll*buc[a[x]]*(buc[a[x]]-1)>>1;}
ll calc(int l,int r){
while(cr<r) push(++cr);
while(cl>l) push(--cl);
while(cl<l) pop(cl++);
while(cr>r) pop(cr--);
return sum;
}
void solve(int l,int r,int pl,int pr,int j){
if(l>r||pl>pr) return;int mid=l+r>>1,pos=-1;
for(int i=pl;i<=min(mid-1,pr);i++){
if(dp[mid][j]>dp[i][j-1]+calc(i+1,mid))
dp[mid][j]=dp[i][j-1]+calc(i+1,mid),pos=i;
} solve(l,mid-1,pl,pos,j);solve(mid+1,r,pos,pr,j);
}
int main(){
scanf("%d%d",&n,&k);memset(dp,63,sizeof(dp));dp[0][0]=0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=k;i++) solve(1,n,0,n,i);
printf("%lld\n",dp[n][k]);
return 0;
}
2. P5574 [CmdOI2019]任務分配問題
和上面差不多,只不過移指標時要用樹狀陣列統計答案,因此時間複雜度 \(n\log^2n\)
3. LOJ #6039. 「雅禮集訓 2017 Day5」珠寶
首先注意到每件物品的代價很小,最多隻有 \(300\),因此可以考慮在代價上做點文章,記 \(dp_{i,j}\) 表示考慮了代價 \(\le i\) 的物品,總代價 \(\le j\) 的最大價值,再記 \(mx_{i,j}\) 表示代價為 \(i\) 的物品中價值最大的 \(j\) 個物品價值之和,那麼顯然有 \(dp_{i,j}=\max\limits_{j-ki\ge 0}dp_{i-1,j-ki}+mx_{i,k}\),注意到 \(mx_{i,k}\) 為下凸函式,因此該 \(dp\) 轉移方程滿足四邊形不等式,在同一個剩餘類(\(\bmod i\) 的同餘系)中滿足決策單調性,對每個剩餘類分治一下即可。
真·\(10^7\log 10^7\) 給艹過去了
4. P2605 [ZJOI2010]基站選址
此題正解似乎不是什麼決策單調性,但貌似決策單調效能水過去
首先考慮求出每個村莊,要使它不交補償費,顯然必須在某個區間中需有基站,我們假設這個區間為 \([L_i,R_i]\),那麼代價函式 \(w(l,r)\) 的定義即為 \(\sum\limits_{i=l}^rw_i[L_i>l][R_i<r]\),不難發現它滿足四邊形不等式,即 \(w(l,r)+w(l+1,r-1)\ge w(l+1,r)+w(l,r-1)\),分治+決策單調性優化即可,統計答案可用莫隊的思想,加入左/右端點時在對應 vector
裡 lower_bound
即可,理論複雜度 \(\mathcal O(nk\log^2n)\),被正解碾壓,但實際跑起來飛快,吊打部分實現不好的正經寫法。
III. 1D1D:二分佇列
適用範圍:\(dp_i=\min\limits_{j<i}dp_j+w(j+1,i)\)。
大概就是,如果某個 \(dp\) 狀態滿足決策單調性,那麼對於某兩個可以轉移到 \(i\) 的決策點 \(j,k(j<k)\),如果 \(j\) 沒有 \(k\) 來得優,那麼隨著 \(i\) 的增大,\(j\) 肯定更沒有 \(k\) 來得優,此時 \(j\) 就沒有用了。那麼考慮以 \(i\) 的增大為時間線,某個決策點 \(j\) 從出生到被幹掉的命運顯然應該是這樣的:開始時不如某些在它前面的決策點,後來不斷變強,逐漸幹掉在它前面的決策點並(有可能)成為最優決策點,在最優決策點保持一段時間後又被它後面的決策點反超。因此考慮維護一個單調佇列,從隊首到隊尾決策點下標單調遞增,同時對 \(i\) 的貢獻單調遞減,那麼顯然隊首元素就是 \(i\) 的最優決策點。當 \(i\) 變為 \(i+1\) 時我們就不斷彈出隊首,直到隊首元素由於佇列第二個元素即可,將某個決策點 \(i\) 加入佇列時,我們就記 \(need(x,y)\) 表示 \(x\) 最早什麼時候能夠比 \(y\) nb,那麼我們比較 \(need(q[tl],q[tl-1])\) 與 \(need(i,q[tl])\),如果 \(i\) 能比 \(q[tl]\) 幹掉 \(q[tl-1]\) 更早地幹掉 \(q[tl]\),那麼 \(q[tl]\) 顯然就是個廢物,彈出佇列即可。\(need(x,y)\) 顯然可以二分求出。
時間複雜度 \(n\log n\)。
5. P1912 [NOI2009] 詩人小G
裸的決策單調性優化 \(dp\),打表可以發現代價函式 \(w\) 滿足四邊形不等式,而狀態轉移方程又滿足 1D1D,因此可以使用二分佇列優化,複雜度 \(n\log n\),注意手寫快速冪並使用 long double
避免精度問題!!!
6. P3515 [POI2011]Lightning Conductor
首先式子可以轉化為 \(p\ge a_j-a_i+\sqrt{|i-j|}\),因此我們只需求出 \(f_i=\max\limits_{j}a_j+\sqrt{|i-j|}\),對於 \(j<i\) 和 \(j>i\) 兩部分顯然是對稱的,因此我們只用著眼於一邊即可,注意到這個 \(w(j,i)=\sqrt{|i-j|}\) 滿足四邊形不等式,因為 \(f(x)=\sqrt{x}\) 為上凸函式,二階導數恆為負,因此可以決策單調性優化,上個決策單調性即可。
const int MAXN=5e5;
int n,a[MAXN+5],q[MAXN+5];
double dp1[MAXN+5],dp2[MAXN+5];
double rt[MAXN+5];
double calc(int x,int y){return a[x]+rt[y-x];}
int need(int x,int y){
int l=x,r=n,p=n+1;
while(l<=r){
int mid=l+r>>1;
if(calc(x,mid)>calc(y,mid)) p=mid,r=mid-1;
else l=mid+1;
} return p;
}
void solve(double *dp){
int hd=1,tl=0;
for(int i=1;i<=n;i++){
while(hd<tl&&need(i,q[tl])<=need(q[tl],q[tl-1])) --tl;
q[++tl]=i;
while(hd<tl&&calc(q[hd],i)<calc(q[hd+1],i)) ++hd;
dp[i]=calc(q[hd],i);
}
}
int main(){
scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) rt[i]=sqrt(i);
solve(dp1);reverse(a+1,a+n+1);solve(dp2);reverse(a+1,a+n+1);
for(int i=1;i<=n;i++) printf("%d\n",max((int)(ceil(max(dp1[i],dp2[n-i+1])))-a[i],0));
return 0;
}
IV. 二維:記錄決策點
適用範圍:二維 \(dp\),滿足決策單調性。
假設 \(dp_{i,j}\) 的決策點 \(from_{i,j}\) 隨著 \(j\) 的增大而增大,也隨著 \(i\) 的增大而增大,那麼不難發現不等式 \(from_{i,j-1}\le from_{i,j}\le from_{i+1,j}\) 一定成立,因此我們正序列舉 \(j\),倒序列舉 \(i\),然後在對應區間內轉移即可,顯然複雜度是嚴格平方的,因為如果我們把 \(dp\) 看作一個矩形,那麼矩形每條對角線上的決策點都是依次遞增的,列舉次數自然也是 \(\mathcal O(n)\) 的,而對角線個數 \(\mathcal O(n)\),因此總複雜度平方。
7. CF321E Ciel and Gondolas
設 \(dp_{i,j}\) 表示前 \(i\) 個人劃分成 \(j\) 段的最小代價,那麼顯然代價函式 \(w(l,r)\) 滿足決策單調性,記錄轉移點轉移即可,轉移時就求個二維字首和,複雜度嚴格平方。
using namespace fastio;
const int MAXN=4000;
const int MAXK=800;
int n,k,a[MAXN+5][MAXN+5],s[MAXN+5][MAXN+5];
int dp[MAXN+5][MAXK+5],from[MAXN+5][MAXK+5];
int sum(int l1,int r1,int l2,int r2){
return s[r1][r2]-s[l1-1][r2]-s[r1][l2-1]+s[l1-1][l2-1];
}
int main(){
read(n);read(k);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
read(a[i][j]);s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
} memset(dp,63,sizeof(dp));dp[0][0]=0;
for(int i=1;i<=k;i++) for(int j=n;j;j--)
for(int l=from[j][i-1];l<=((j==n)?n:from[j+1][i]);l++)
if(l<j&&dp[j][i]>dp[l][i-1]+sum(l+1,j,l+1,j))
dp[j][i]=dp[l][i-1]+sum(l+1,j,l+1,j),from[j][i]=l;
printf("%d\n",dp[n][k]>>1);
return 0;
}
8. P4767 [IOI2000]郵局
同上,唯一的區別是代價函式的計算方式不同&輸入格式不同(大霧)
9. P5897 [IOI2013]wombats
nb tea,線段樹分塊+決策單調性優化 dp,題解
參考資料:
-
DP的決策單調性優化總結,cmd_blk yyds!
2. wqs 二分
基本思想
似乎跟決策單調性關係不算太大,但是與決策單調性一樣都屬於通過觀察 \(dp\) 陣列的性質來優化 \(dp\),因此就在這裡一併講掉了(
引入:給定一個由正整陣列成的序列 \(a\),你可以把它分成 \(k\) 段,使每段和的平方之和最小。
我會暴力 \(dp\)!\(dp_{i,j}\) 表示前 \(i\) 個數分成 \(j\) 段的最小代價,複雜度 \(n^3\)!
太慢了!有沒有快一點的做法?
我會決策單調性優化 \(dp\)!這東西的代價函式滿足四邊形不等式,可以分治優化,複雜度 \(nk\log n\)!
還是慢了!有沒有再快一點的做法?
我會斜率優化!這東西可以寫成一次函式的形式,斜率優化一下可以到 \(\mathcal O(nk)\)!
能不能再快一點!
………………
這時候我們的 wqs 二分就要派上用場了。wqs 二分,又稱忘情水王欽石二分,帶權二分,DP 凸優化,常見於限制選取物品個數的 DP 當中(或者限制劃分成 \(k\) 段),如果我們發現 \(dp_i\) 滿足凹凸性(上凸/下凸),那麼就可以考慮 wqs 二分。
關於 wqs 二分一個比較感性的認識是,以剛才的例子為例,如果我們不限制段數,那麼根據 \((\sum\limits_{i=1}^na_i)^2\ge\sum\limits_{i=1}^na_i^2\) 可知我們肯定會貪心地選擇每個元素單獨一段,這樣可能不滿足 \(k\) 的限制,因此我們考慮加上一個額外的條件:每多分一段就可以 \(-c\) 的代價,那麼顯然如果 \(c\) 趨近於 \(\infty\) 我們就每個元素自己單獨成一段了,而 \(c=0\) 就是之前的情形,那麼我們能不能找到一個分界點,使得在上述情形中最優方案剛好分了 \(k\) 段呢?答案是肯定的,這就是 wqs 二分的基本思想。因此如果做題拿不準是否是 wqs 二分可以用該方法感性地判斷,或者實在不行打個表觀察一下也行。
感性的認識講完了,下面講理性的理解,還是以上面的例子為例,下面簡記 \(f(x)\) 表示上面的 \(dp_{n,x}\),打個表發現 \(f(x)\) 是個凸函式,因此考慮二分一個斜率 \(c\) 並用斜率為 \(-c\) 的直線去截這個凸包,那麼該直線一定會卡到凸包下方的某個點,這個點顯然一定是在 \(y\) 軸上截距最小的點——或者說,就是上面感性認識中,我們給每段代價 \(-c\) 後最優劃分方案,我們考慮求出這個最優劃分方案——這個就可以按照上面暴力做法那樣決策單調性/斜率優化了,同時我們記錄最優方案劃分的段數 \(p\),如果 \(p=k\) 那顯然本題就做完了,答案就是 \(\text{最優劃分策略的答案}+kc\),否則如果 \(p>k\) 那我們就將 \(c\) 調大一點,如果 \(p<k\) 就將 \(c\) 調小一點,這樣本題就做完了,複雜度 \(n\log n/n\log^2n\),吊打上面的三個做法。
重要實現細節:三點共線
上述做法夢想很美滿,現實很骨感,你照著上面的思路寫,過了樣例,一交……
WA……WA?WA!
原因是因為你沒有特殊判斷下面的情形:
在上面的情形中,假如我們的 \(k\) 剛好就是 \(C\) 點的橫座標,那麼在 \(c\) 稍微大一點的情況下會切到 \(B\)(即上圖中的橙色直線 \(k_1\)),\(c\) 稍微小一點的情況下會切到 \(D\)(即上圖中的粉色線段),永遠也輪不到 \(C\),所以上面的狀態還有待改進,我們考慮在最優答案相同時,保留劃分段數最大的答案,然後如果劃分段數 \(\ge k\) 就更新答案,並且更新答案為最終最優解 \(+k\times mid\),而不是最優解 \(+\text{最優劃分段數}\times k\)。為什麼要限制這個劃分段數最大/最小呢,因為顯然直線在切到 \(B\) 和切到 \(D\) 之間總會有一個斜率的分界點對吧,我們的答案肯定會取分界點左邊減去一個很小的值 \(\epsilon\) 對吧,那由於它大於真正的 \(BD\) 之間的斜率 \(k\),因此它總會切到 \(D\),但由於它與真正的分界點之間的差太微小了,我們的程式會把它與 \(B\) 處的答案認作同一個值,那麼怎麼辦呢……這時候就要以劃分段數為第二關鍵字了,如果我們沒有這個操作那切到 \(B\) 時候答案就不會被更新了,這顯然是我們所不希望的,加上這個操作就可以避免這個 error 了。當然你也可以保留劃分段數最小的答案,那麼此時就要在劃分段數 \(\le k\) 時更新答案,這樣就可以過了。
其他細節
- 在 wqs 二分中,由於 \(dp\) 值一般是整數,因此斜率 \(c=\dfrac{dp_{i+1}-dp_i}{i+1-i}=dp_{i+1}-dp_i\) 也是整數,因此二分可以不用實數域上二分,但如果碰到小數的 \(dp\) 值就要實數域二分了。
- 可以將 \((\text{代價},\text{劃分段數})\) 封成一個類,手動過載小於號和加號,這樣可以省去不少繁瑣的比較操作,但可能常數會有億點大(因為加了類封裝後常數本來就會變大)
wqs 二分與費用流的關係
眾所周知,
如果是一個費用流模型,它肯定具有下凸性。
因為費用流的過程中,肯定是先增廣最短路,增廣完了之後不可能增廣更短的路。——
某位我不認識的神仙
說人話,就是假設我們每次增廣流量為 \(1\) 的流,那麼顯然每次增廣時我們會選擇一條 \(S\to T\) 的最短路徑,並令答案加上這條路徑的權值之和,由於增廣之後會減少正向邊的權值,因此對於一條 \(S\to T\) 的路徑,如果我們第 \(k\) 次增廣時增廣了它,那麼顯然 \(\forall i\in[1,k-1]\),第 \(i\) 次增廣時這條路徑也在圖上,因此第 \(i\) 次增廣的權值 \(g(i)\) 是一個遞增函式,前 \(i\) 次增廣的路徑之和 \(f(i)\) 自然就是一個下凸函式。所以如果多次增廣的費用流問題,我們可以用 wqs 二分來優化。
例題
10. P2619 [國家集訓隊]Tree I
記 \(f_i\) 為選 \(i\) 條白邊的答案,那麼我們感性理解一下,如果我們給每條白邊權值 \(-\infty\),那麼顯然選擇的白邊條數會取到最大值;如果給它們都加上 \(\infty\),那麼選擇的白邊條數會取到最小值——也就是說存在一個分界點 \(c\) 使得給每條白邊權值 \(-c\) 後最小生成樹中恰好有 \(k\) 條白邊,故 \(f_i\) 為凸函式,wqs 二分即可,複雜度 \(n\log^2n\),可以使用歸併排序,即將黑邊白邊在預處理時分別排序,每次二分時歸併一下即可,配合路徑壓縮+啟發式合併可以做到 \(\mathcal O(n\alpha(n)\log n)\),沒有興趣實現……
const int MAXN=5e4;
const int MAXM=1e5;
int n,m,need,ans;
struct edge{
int u,v,w,col;
bool operator <(const edge &rhs){
return (w^rhs.w)?(w<rhs.w):(col<rhs.col);
}
} e[MAXM+5];
int f[MAXN+5];
int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
bool check(int mid){
for(int i=1;i<=n;i++) f[i]=0;
for(int i=1;i<=m;i++) if(!e[i].col) e[i].w-=mid;
sort(e+1,e+m+1);int tot=0,res=0;
for(int i=1;i<=m;i++){
int fu=find(e[i].u),fv=find(e[i].v);
if(fu==fv) continue;f[fu]=fv;
tot+=!e[i].col;res+=e[i].w;
} for(int i=1;i<=m;i++) if(!e[i].col) e[i].w+=mid;
return (tot>=need)?(ans=res+mid*need,1):0;
}
int main(){
scanf("%d%d%d",&n,&m,&need);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].col);
++e[i].u;++e[i].v;
} int l=-100,r=100,p=0;
while(l<=r){
int mid=l+r>>1;
if(check(mid)) p=mid,r=mid-1;
else l=mid+1;
} printf("%d\n",ans);
return 0;
}
11. CF739E Gosha is hunting
首先三方的 \(dp\) 顯然:\(dp_{i,a,b}\) 表示取前 \(i\) 個球的過程中中用了 \(a\) 個寶貝球和 \(b\) 個超級球的最小代價,顯然可以 wqs 二分,於是上個 wqs 二分找最優劃分段數為 \(B\) 的斜率即可,時間複雜度平方對數,可以通過 wqs 二分套 wqs 二分做到 \(n\log^2n\),有興趣的可以自己去實現一下,反正我是從來沒有興趣咯……
通過以下程式碼瞭解一下決策單調性 \(dp\) 陣列的類如何實現:
const int MAXN=2000;
const double EPS=1e-12;
int n,x,y;double a[MAXN+5],b[MAXN+5],ans=0;
struct dp_val{
int num;double res;
dp_val(int _num=0,double _res=0):num(_num),res(_res){}
bool operator <(dp_val rhs) const{
return (fabs(res-rhs.res)<EPS)?(num>rhs.num):(res<rhs.res);
}
dp_val operator +(dp_val rhs) const{
return dp_val(num+rhs.num,res+rhs.res);
}
} dp[MAXN+5][MAXN+5];
int main(){
scanf("%d%d%d",&n,&x,&y);
for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
for(int i=1;i<=n;i++) scanf("%lf",&b[i]);
double l=0,r=1;
while(fabs(r-l)>EPS){
double mid=1.0*(l+r)/2;
for(int i=1;i<=n;i++) for(int j=0;j<=y;j++) dp[i][j]=dp_val();
for(int i=1;i<=n;i++) for(int j=0;j<=y;j++){
chkmax(dp[i][j],dp[i-1][j]+dp_val(0,0));
chkmax(dp[i][j],dp[i-1][j]+dp_val(1,a[i]-mid));
if(j){
chkmax(dp[i][j],dp[i-1][j-1]+dp_val(0,b[i]));
chkmax(dp[i][j],dp[i-1][j-1]+dp_val(1,a[i]+b[i]-a[i]*b[i]-mid));
}
} dp_val mx;
for(int j=0;j<=y;j++) chkmax(mx,dp[n][j]);
if(mx.num<=x) r=mid,ans=mx.res+x*mid;
else l=mid;
} printf("%.10lf\n",ans);
return 0;
}
此外,此題還可以使用費用流求解:新建兩個點 \(A,B\),從 \(S\) 向 \(A\) 連容量 \(a\) 權值 \(0\) 的邊,向 \(B\) 連容量 \(b\) 權值 \(0\) 的邊,從 \(A\) 向每個球 \(i\) 連容 \(1\) 權值 \(p_i\) 的邊;從 \(B\) 向每個球連容 \(1\) 權值 \(u_i\) 的邊,然後從 \(i\) 向 \(T\) 連兩條邊,容量均為 \(1\),一條權值 \(0\),一條權值 \(-u_ip_i\),然後跑最大費用最大流即可。
12. P4383 [八省聯考2018]林克卡特樹
首先題目等價於選擇 \(k+1\) 條不同的鏈,問它們邊權之和的最大值,打個表可以發現該 \(dp\) 為凸函式,因此可以 wqs 二分。
二分一個斜率 \(k\),然後設 \(dp_{u,0/1/2}\) 表示考慮了 \(u\) 的子樹,\(u\) 下方連了 \(0/1/2\) 條邊的最大權值之和,再設 \(f_u\) 表示以 \(u\) 為根的子樹中,與 \(u\) 的父親不連邊時的答案。轉移就列舉對應的兒子的狀態合併一下即可,具體來說轉移方程如下(其中 \(w\) 為 \((u,v)\) 邊的權值):
- \(dp_{u,2}=\max\{dp_{u,2},dp_{u,1}+dp_{v,1}+w-mid,dp_{u,1}+f_{v}\}\)
- \(dp_{u,1}=\max\{dp_{u,1},dp_{u,0}+dp_{v,1}+w-mid,dp_{u,0}+f_v\}\)
- \(dp_{u,0}=dp_{u,0}+dp_{v,0}\)
最後 \(f_u=\max\{dp_{u,0},dp_{u,1}-mid,dp_{u,2}\}\)
注意轉移順序,應按照 \(2\to 1\to 0\) 的順序轉移,還有二分斜率有可能是負的,因此二分下界不能設為 \(0\)!!!!111
13. CF802O April Fools' Problem (hard)
並不是愚人節的題目(大霧
首先此題顯然可以 wqs 二分否則我就不會把它放在這裡了(London Fog
對於每一道題有三種選擇:
- 跳過它,不使用它
- 把它當作一個準備的決策
- 把它和之前最小的 \(a\) 匹配,並列印
我們可以用優先佇列來維護這個過程,時間複雜度 \(n\log n\log w\)。
14. CF958E2 Guard Duty (medium)
還是按照套路 wqs 二分,然後二分內部是一個 dp,樸素 dp 是平方的,稍微用點腦子就知道可以使用字首 \(\min\) 優化,複雜度 \(n\log n\)。
15. P4983 忘情
首先將題目中那個奇奇怪怪的貢獻柿子拆開可得 \(w(l,r)=(1+\sum\limits_{i=l}^ra_i)^2\),然後按照套路 wqs 二分即可,wqs 二分內部我們設 \(dp_i\) 表示劃分前 \(i\) 個數的最小代價,那麼有 \(dp_i=\sum\limits_{j<i}w(j+1,i)+dp_j\),斜率優化即可,複雜度 \(n\log n\)。
16. P6246 [IOI2000] 郵局 加強版
沒錯就是 \(8\) 的加強版,不過學了 wqs 二分這個加強版和原題就沒啥區別了……
上個 wqs 二分,然後決策單調性優化一下即可。
由於轉移方程是 1D1D,因此需要二分佇列
17. P5633 最小度限制生成樹
如果把每個點的顏色看作它是否有一個端點為 \(s\),那麼就跟 \(10\) 一樣了……
注意判 Impossible
!
18. P5308 [COCI2019] Quiz
上凸殼寫成下凸殼了都能過樣例,就 nm 離譜
首先如果不考慮 \(k\) 的限制那麼可以考慮倒著 \(dp\),\(dp_i\) 表示還剩 \(i\) 個人最多能夠獲得的獎金,那麼有 \(dp_i=\max\limits_{j<i}dp_j+\dfrac{i-j}{i}\),發現這東西可以斜率優化,即對於 \(j<k\),從 \(k\) 轉移比從 \(j\) 轉移更優的充要條件是 \(dp_j+\dfrac{i-j}{i}<dp_k+\dfrac{i-k}{i}\),即 \(\dfrac{dp_j-dp_k}{j-k}>\dfrac{1}{i}\),斜率優化一下即可。加個 \(k\) 的限制之後上個 wqs 即可。
19. P5617 [MtOI2019]不可視境界線 / 2021.7.14 六校聯訓 T2 / NFLSOJ #1055
真·昨天剛學的 wqs 二分今天就能在模擬賽中派上用場
傻逼卡精度+卡常屑題
首先一眼 wqs 二分,二分內部可以 \(dp\),\(dp_i\) 表示前 \(i\) 個圓覆蓋的並減去 \(mid\) 乘圓的個數的最大值,那麼顯然有轉移方程 \(dp_i=\max\limits_{j<i}dp_j+w(j,i)-mid\),其中 \(w(j,i)\) 是一個與 \(x_i-x_j\) 有關的函式,可以通過 cmath
庫預處理出來,發現它是一個下凸函式,因此莽個決策單調性即可。
時間複雜度 \(n\log n\log w\),然鵝由於涉及浮點數的運算,常數上天……
參考資料: