dp凸优化/wqs二分学习笔记(洛谷4383 [八省联考2018]林克卡特树lct)
qwq
安利一个凸优化讲的比较好的博客
https://www.cnblogs.com/Gloid/p/9433783.html
但是他的暴力部分略微有点问题
qwq
我还是详细的讲一下这个题+这个知识点吧。
还是先从题目入手。
首先我们分析题目。
因为题目要删除\(k\)条边,然后再新建\(k\)条边,求两点的路径和。
那我们不妨这么考虑,对于新连接一条边,相当于链接了原树上的两条链,且链不存在交点。
那我们新建\(k\)条边,就相当于把原树上没有交的\(k+1\)条链连接起来。
既然要求权值最大。
那我们就可以直接把题目转换成求树上选出点不相交的\(k+1\)条链的最大收益。(每个点都是一条链)。
qwq
首先考虑应该怎么
暴力\(dp\)
由于对于\(x\)这颗子树,要分情况讨论他属于一条链的端点,中间点,还是不属于链。
所以我们定义状态\(dp[i][j][0/1/2]\)表示\(i\)的子树里,已经选了\(j\)条链,其中\(i\)这个点不属于链,属于一个链的端点,属于一个链的中心点的最大收益。
首先,因为负权,所以要把所有的\(dp\)弄成初始值是\(-inf\)的,其中\(dp[i][0][0]\)为0。
然后我们考虑应该怎么转移。
对于一个点来说,我们先枚举他的儿子,然后枚举他子树内的链数\(j\),枚举分配给他的儿子的链数\(z\)
那么对于不同的\(0/1/2\)我们需要分情况讨论。
对于\(0\)来说,他可以从儿子的任意一个状态转移过来。
对于\(1\)来说,首先他可以由当前状态的\(1\)+儿子的\(0/1/2\)中最大的那个转移,表示不与当前的儿子构成链。
也可以从当前状态的0+儿子的1+\(val[i]\),表示从当前儿子上来一条链。
另外的一种情况就是只选与当前儿子相连的边。
对于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])));//和当前构成一条链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\)要倒着枚举,因为一个链只能被选一次(防止出现旧自己更新新自己的情况)
其次\(z\)要循环到0(只是用来针对只算一条边的情况。)
然后通过以上的过程,我们就能轻松愉悦的通过\(O(nk)\)的60分了。
那么应该怎么优化这个过程呢。
凸优化
首先,我们通过做差分,即\(ans[i][j]-ans[i][j-1]\),\(ans[i][j]\)表示\(n=i,k=j\)时候的答案,发现斜率是逐渐降低的,那我们不难发现在\(i\)一定的情况下,其实\(ans\)是关于\(j\)的上凸函数。(并且一定要满足斜率单调!!!!!!)、
那我们实际上就是要求出来在\(j=k\)的时候的那个对应的凸包的点是多少。
根据斜率单调
我们可以直接二分一个\(mid\),然后让所有的边权都加上\(mid\),然后不限制选的链的条数,求最大收益和链数,这个复杂度是\(O(n)\)的。
因为我们发现,当\(mid= inf\)的时候,一定是选\(n\)条,当\(inf=-mid\)的时候,是选0条,那么因为斜率单调(所以选择的条数一定是随着mid单调变大的),所以我们一定能通过改变这个\(mid\),存在某一个\(mid\)满足恰好选择\(k\)条,那么正好符合题目要求。
另外一种理解方式。
qwq这个理解方式,每次要减去\(mid\),其实本质是一样的。
我们相当于对于每个点求出他的正上方所对应的凸包的点是啥,那么我们通过\(erf\)这个东西,相当于把原图的上的每个点\(x\)向下移动\(mid*x\),那么我们可以发现,随着\(mid\)的变大,每次随便选的取到的收益的最高的地方,是单调右移的。我们只需要记录一下选的最大收益和链数,然后找到链数等于\(k\)所对应的\(mid\),计算一遍贡献就行。
至于如何做不限制条数的收益,和\(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});//用一条边合并两个链(链数会减一)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; }转载于:https://www.cnblogs.com/yimmortal/p/10197670.html
总结
以上是生活随笔为你收集整理的dp凸优化/wqs二分学习笔记(洛谷4383 [八省联考2018]林克卡特树lct)的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: C# 常用类-IO-ClassXML
- 下一篇: IdentityServer4-EF动态