dp凸优化/wqs二分学习笔记(洛谷4383 [八省联考2018]林克卡特树lct)
qwq
安利一個凸優(yōu)化講的比較好的博客
https://www.cnblogs.com/Gloid/p/9433783.html
但是他的暴力部分略微有點問題
qwq
我還是詳細(xì)的講一下這個題+這個知識點吧。
還是先從題目入手。
首先我們分析題目。
因為題目要刪除\(k\)條邊,然后再新建\(k\)條邊,求兩點的路徑和。
那我們不妨這么考慮,對于新連接一條邊,相當(dāng)于鏈接了原樹上的兩條鏈,且鏈不存在交點。
那我們新建\(k\)條邊,就相當(dāng)于把原樹上沒有交的\(k+1\)條鏈連接起來。
既然要求權(quán)值最大。
那我們就可以直接把題目轉(zhuǎn)換成求樹上選出點不相交的\(k+1\)條鏈的最大收益。(每個點都是一條鏈)。
qwq
首先考慮應(yīng)該怎么
暴力\(dp\)
由于對于\(x\)這顆子樹,要分情況討論他屬于一條鏈的端點,中間點,還是不屬于鏈。
所以我們定義狀態(tài)\(dp[i][j][0/1/2]\)表示\(i\)的子樹里,已經(jīng)選了\(j\)條鏈,其中\(i\)這個點不屬于鏈,屬于一個鏈的端點,屬于一個鏈的中心點的最大收益。
首先,因為負(fù)權(quán),所以要把所有的\(dp\)弄成初始值是\(-inf\)的,其中\(dp[i][0][0]\)為0。
然后我們考慮應(yīng)該怎么轉(zhuǎn)移。
對于一個點來說,我們先枚舉他的兒子,然后枚舉他子樹內(nèi)的鏈數(shù)\(j\),枚舉分配給他的兒子的鏈數(shù)\(z\)
那么對于不同的\(0/1/2\)我們需要分情況討論。
對于\(0\)來說,他可以從兒子的任意一個狀態(tài)轉(zhuǎn)移過來。
對于\(1\)來說,首先他可以由當(dāng)前狀態(tài)的\(1\)+兒子的\(0/1/2\)中最大的那個轉(zhuǎn)移,表示不與當(dāng)前的兒子構(gòu)成鏈。
也可以從當(dāng)前狀態(tài)的0+兒子的1+\(val[i]\),表示從當(dāng)前兒子上來一條鏈。
另外的一種情況就是只選與當(dāng)前兒子相連的邊。
對于2來說,其實也是和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]))); }}} }這里有兩個需要注意的問題,首先是\(j\)要倒著枚舉,因為一個鏈只能被選一次(防止出現(xiàn)舊自己更新新自己的情況)
其次\(z\)要循環(huán)到0(只是用來針對只算一條邊的情況。)
然后通過以上的過程,我們就能輕松愉悅的通過\(O(nk)\)的60分了。
那么應(yīng)該怎么優(yōu)化這個過程呢。
凸優(yōu)化
首先,我們通過做差分,即\(ans[i][j]-ans[i][j-1]\),\(ans[i][j]\)表示\(n=i,k=j\)時候的答案,發(fā)現(xiàn)斜率是逐漸降低的,那我們不難發(fā)現(xiàn)在\(i\)一定的情況下,其實\(ans\)是關(guān)于\(j\)的上凸函數(shù)。(并且一定要滿足斜率單調(diào)!!!!!!)、
那我們實際上就是要求出來在\(j=k\)的時候的那個對應(yīng)的凸包的點是多少。
根據(jù)斜率單調(diào)
我們可以直接二分一個\(mid\),然后讓所有的邊權(quán)都加上\(mid\),然后不限制選的鏈的條數(shù),求最大收益和鏈數(shù),這個復(fù)雜度是\(O(n)\)的。
因為我們發(fā)現(xiàn),當(dāng)\(mid= inf\)的時候,一定是選\(n\)條,當(dāng)\(inf=-mid\)的時候,是選0條,那么因為斜率單調(diào)(所以選擇的條數(shù)一定是隨著mid單調(diào)變大的),所以我們一定能通過改變這個\(mid\),存在某一個\(mid\)滿足恰好選擇\(k\)條,那么正好符合題目要求。
另外一種理解方式。
qwq這個理解方式,每次要減去\(mid\),其實本質(zhì)是一樣的。
我們相當(dāng)于對于每個點求出他的正上方所對應(yīng)的凸包的點是啥,那么我們通過\(erf\)這個東西,相當(dāng)于把原圖的上的每個點\(x\)向下移動\(mid*x\),那么我們可以發(fā)現(xiàn),隨著\(mid\)的變大,每次隨便選的取到的收益的最高的地方,是單調(diào)右移的。我們只需要記錄一下選的最大收益和鏈數(shù),然后找到鏈數(shù)等于\(k\)所對應(yīng)的\(mid\),計算一遍貢獻(xiàn)就行。
至于如何做不限制條數(shù)的收益,和\(nk\)類似的\(dp\)
感覺這個東西要感性+理性啊
#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};//可以將一個點看成一條鏈 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});//用一條邊合并兩個鏈(鏈數(shù)會減一)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});//新增一個鏈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)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 常用类-IO-ClassXML
- 下一篇: IdentityServer4-EF动态