dp凸优化/wqs二分学习笔记(洛谷4383 [八省联考2018]林克卡特树lct)
qwq
安利一個(gè)凸優(yōu)化講的比較好的博客
https://www.cnblogs.com/Gloid/p/9433783.html
但是他的暴力部分略微有點(diǎn)問(wèn)題
qwq
我還是詳細(xì)的講一下這個(gè)題+這個(gè)知識(shí)點(diǎn)吧。
還是先從題目入手。
首先我們分析題目。
因?yàn)轭}目要?jiǎng)h除\(k\)條邊,然后再新建\(k\)條邊,求兩點(diǎn)的路徑和。
那我們不妨這么考慮,對(duì)于新連接一條邊,相當(dāng)于鏈接了原樹(shù)上的兩條鏈,且鏈不存在交點(diǎn)。
那我們新建\(k\)條邊,就相當(dāng)于把原樹(shù)上沒(méi)有交的\(k+1\)條鏈連接起來(lái)。
既然要求權(quán)值最大。
那我們就可以直接把題目轉(zhuǎn)換成求樹(shù)上選出點(diǎn)不相交的\(k+1\)條鏈的最大收益。(每個(gè)點(diǎn)都是一條鏈)。
qwq
首先考慮應(yīng)該怎么
暴力\(dp\)
由于對(duì)于\(x\)這顆子樹(shù),要分情況討論他屬于一條鏈的端點(diǎn),中間點(diǎn),還是不屬于鏈。
所以我們定義狀態(tài)\(dp[i][j][0/1/2]\)表示\(i\)的子樹(shù)里,已經(jīng)選了\(j\)條鏈,其中\(i\)這個(gè)點(diǎn)不屬于鏈,屬于一個(gè)鏈的端點(diǎn),屬于一個(gè)鏈的中心點(diǎn)的最大收益。
首先,因?yàn)樨?fù)權(quán),所以要把所有的\(dp\)弄成初始值是\(-inf\)的,其中\(dp[i][0][0]\)為0。
然后我們考慮應(yīng)該怎么轉(zhuǎn)移。
對(duì)于一個(gè)點(diǎn)來(lái)說(shuō),我們先枚舉他的兒子,然后枚舉他子樹(shù)內(nèi)的鏈數(shù)\(j\),枚舉分配給他的兒子的鏈數(shù)\(z\)
那么對(duì)于不同的\(0/1/2\)我們需要分情況討論。
對(duì)于\(0\)來(lái)說(shuō),他可以從兒子的任意一個(gè)狀態(tài)轉(zhuǎn)移過(guò)來(lái)。
對(duì)于\(1\)來(lái)說(shuō),首先他可以由當(dāng)前狀態(tài)的\(1\)+兒子的\(0/1/2\)中最大的那個(gè)轉(zhuǎn)移,表示不與當(dāng)前的兒子構(gòu)成鏈。
也可以從當(dāng)前狀態(tài)的0+兒子的1+\(val[i]\),表示從當(dāng)前兒子上來(lái)一條鏈。
另外的一種情況就是只選與當(dāng)前兒子相連的邊。
對(duì)于2來(lái)說(shuō),其實(shí)也是和1同理。
void solve(int x,int fa) { for (int i=point[x];i;i=nxt[i]){int p = to[i];if(p==fa) continue;solve(p,x);for (int j=min(k,size[x]);j>=1;j--){for (int z=0;z<=min(j,size[p]);z++){dp[x][j][2]=max(dp[x][j][2],dp[x][j-z][2]+max(dp[p][z][0],max(dp[p][z][1],dp[p][z][2])));dp[x][j][2]=max(dp[x][j][2],dp[x][j-z][1]+dp[p][z+1][1]+val[i]);if (j>z) dp[x][j][2]=max(dp[x][j][2],dp[x][j-z][1]+val[i]+dp[p][z][0]);//本身就有1一條鏈 dp[x][j][1]=max(dp[x][j][1],dp[x][j-z][1]+max(dp[p][z][0],max(dp[p][z][1],dp[p][z][2])));//和當(dāng)前構(gòu)成一條鏈dp[x][j][1]=max(dp[x][j][1],dp[x][j-z][0]+dp[p][z][1]+val[i]);if (j>z) dp[x][j][1]=max(dp[x][j][1],dp[x][j-z-1][0]+dp[p][z][0]+val[i]); dp[x][j][0]=max(dp[x][j][0],dp[x][j-z][0]+max(dp[p][z][0],max(dp[p][z][1],dp[p][z][2]))); }}} }這里有兩個(gè)需要注意的問(wèn)題,首先是\(j\)要倒著枚舉,因?yàn)橐粋€(gè)鏈只能被選一次(防止出現(xiàn)舊自己更新新自己的情況)
其次\(z\)要循環(huán)到0(只是用來(lái)針對(duì)只算一條邊的情況。)
然后通過(guò)以上的過(guò)程,我們就能輕松愉悅的通過(guò)\(O(nk)\)的60分了。
那么應(yīng)該怎么優(yōu)化這個(gè)過(guò)程呢。
凸優(yōu)化
首先,我們通過(guò)做差分,即\(ans[i][j]-ans[i][j-1]\),\(ans[i][j]\)表示\(n=i,k=j\)時(shí)候的答案,發(fā)現(xiàn)斜率是逐漸降低的,那我們不難發(fā)現(xiàn)在\(i\)一定的情況下,其實(shí)\(ans\)是關(guān)于\(j\)的上凸函數(shù)。(并且一定要滿足斜率單調(diào)!!!!!!)、
那我們實(shí)際上就是要求出來(lái)在\(j=k\)的時(shí)候的那個(gè)對(duì)應(yīng)的凸包的點(diǎn)是多少。
根據(jù)斜率單調(diào)
我們可以直接二分一個(gè)\(mid\),然后讓所有的邊權(quán)都加上\(mid\),然后不限制選的鏈的條數(shù),求最大收益和鏈數(shù),這個(gè)復(fù)雜度是\(O(n)\)的。
因?yàn)槲覀儼l(fā)現(xiàn),當(dāng)\(mid= inf\)的時(shí)候,一定是選\(n\)條,當(dāng)\(inf=-mid\)的時(shí)候,是選0條,那么因?yàn)樾甭蕟握{(diào)(所以選擇的條數(shù)一定是隨著mid單調(diào)變大的),所以我們一定能通過(guò)改變這個(gè)\(mid\),存在某一個(gè)\(mid\)滿足恰好選擇\(k\)條,那么正好符合題目要求。
另外一種理解方式。
qwq這個(gè)理解方式,每次要減去\(mid\),其實(shí)本質(zhì)是一樣的。
我們相當(dāng)于對(duì)于每個(gè)點(diǎn)求出他的正上方所對(duì)應(yīng)的凸包的點(diǎn)是啥,那么我們通過(guò)\(erf\)這個(gè)東西,相當(dāng)于把原圖的上的每個(gè)點(diǎn)\(x\)向下移動(dòng)\(mid*x\),那么我們可以發(fā)現(xiàn),隨著\(mid\)的變大,每次隨便選的取到的收益的最高的地方,是單調(diào)右移的。我們只需要記錄一下選的最大收益和鏈數(shù),然后找到鏈數(shù)等于\(k\)所對(duì)應(yīng)的\(mid\),計(jì)算一遍貢獻(xiàn)就行。
至于如何做不限制條數(shù)的收益,和\(nk\)類似的\(dp\)
感覺(jué)這個(gè)東西要感性+理性啊
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<map> #include<set> #define mk make_pair #define ll long long #define int long long using namespace std; inline int read() {int x=0,f=1;char ch=getchar();while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f; } const int maxn = 3e5+1e2; const int maxm = 2*maxn; const int inf = 1e12; int point[maxn],nxt[maxm],to[maxm],val[maxm]; int n,m,cnt; int l,r,ans,k; struct ymh{int val,num;ymh operator + (ymh b){return (ymh){val+b.val,num+b.num};} }; ymh max(ymh a,ymh b) {if(a.val==b.val) {if(a.num<b.num) return a;else return b;}else{if(a.val<b.val) return b;else return a;} } void addedge(int x,int y,int w) {nxt[++cnt]=point[x];to[cnt]=y;val[cnt]=w;point[x]=cnt; } ymh dp[maxn][3]; void dfs(int x,int fa,int lim) {dp[x][0]=(ymh){0,0};dp[x][1]=(ymh){-inf,-inf};//不存在這種情況 dp[x][2]=(ymh){lim,1};//可以將一個(gè)點(diǎn)看成一條鏈 for (int i=point[x];i;i=nxt[i]){int p = to[i];if (p==fa) continue;dfs(p,x,lim); ymh now = max(dp[p][0],max(dp[p][1],dp[p][2]));dp[x][2]=max(dp[x][2],dp[x][2]+now); //本身就有鏈 dp[x][2]=max(dp[x][2],dp[x][1]+dp[p][0]+(ymh){val[i],0}); //新加一條邊 dp[x][2]=max(dp[x][2],dp[x][1]+dp[p][1]+(ymh){val[i]-lim,-1});//用一條邊合并兩個(gè)鏈(鏈數(shù)會(huì)減一)dp[x][1]=max(dp[x][1],dp[x][1]+now);dp[x][1]=max(dp[x][1],dp[x][0]+dp[p][1]+(ymh){val[i],0});//沿著之前的鏈 dp[x][1]=max(dp[x][1],dp[x][0]+dp[p][0]+(ymh){val[i]+lim,1});//新增一個(gè)鏈dp[x][0]=max(dp[x][0],dp[x][0]+now); } } signed main() {n=read(),k=read();for (int i=1;i<n;i++){int x=read(),y=read(),w=read();addedge(x,y,w);addedge(y,x,w);r+=w;}k++;r=inf,l=-inf;while(l<=r){int mid = l+r >> 1;memset(dp,0,sizeof(dp));dfs(1,0,mid);ymh now = max(dp[1][0],max(dp[1][1],dp[1][2]));if (now.num<=k) ans=mid,l=mid+1;else r=mid-1;}memset(dp,0,sizeof(dp));dfs(1,0,ans);ymh now = max(dp[1][0],max(dp[1][1],dp[1][2]));cout<<now.val-k*ans;return 0; }轉(zhuǎn)載于:https://www.cnblogs.com/yimmortal/p/10197670.html
總結(jié)
以上是生活随笔為你收集整理的dp凸优化/wqs二分学习笔记(洛谷4383 [八省联考2018]林克卡特树lct)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C# 常用类-IO-ClassXML
- 下一篇: IdentityServer4-EF动态