首页 > 其他分享 >DP优化——wqs二分

DP优化——wqs二分

时间:2024-09-05 20:36:01浏览次数:10  
标签:二分 wqs 凸包 斜率 DP Kx dp

在看 wqs 二分前建议先去看另一篇博客——斜率优化,对凸包等知识点有所了解。

介绍

wqs 二分最初由王钦石在他的 2012 年国家集训队论文中提出,也叫"带权二分",或者"dp凸优化",而从 IOI 2016 的 Aliens 题目开始,这种方法开始逐步在竞赛圈中有了一定的地位。在国内我们一般称为「wqs 二分」,而在国外一般称为「Alien Trick」。

适用题型

wqs 二分的题目一般有以下特点:

  • 题目内容形式为:有 \(n\) 个物品,从中选出 \(m\) 个,每个要求最后的权值最小/最大。
  • 直接 dp 设 \(f[i][j]\) 表示前 \(i\) 个选出 \(j\) 个物品的话,转移是 \(f[i][j]=min_k(f[k][j-1] + Val(k,i))\),其中\(Val(k,i)\) 表示这次转移带来的权值。时间复杂度无论如何都是 \(O(n^2)\) 及以上的。
  • 如果没有选 \(m\) 这个限制,那可以优化到更低复杂度,并且可以算出此时最优方案选的数的个数。
  • 选的个数越多,最终权值越小/越大,即如果设 \(g(x)\),表示选 \(x\) 可以得到的最小/最大圈子,那么 \(g(x)\) 的图像构成一个凸包。

当然 wqs 二分不止应用于 DP 题,具体看例题。

解法

假设 \(g(x)\) 的图像为上凸包,要求的是最大值,不妨画一下 \(g(x)\) 的大致图像(当然其实我们是一个点都求不出来的):

假设我们现在用一条直线 \(y=Kx+b\) 去切一个点 \((x,g(x))\),那么可以得到 \(g(x)=Kx+b\),即这个点的坐标也可以表示成 \((x,Kx+b)\)。
又因为上凸包有个性质,一条斜率为 \(K\) 的直线在他与这个凸包的切点处截距最大,也就是说如果我们能求出这个最大截距,并知道此时的横坐标,就能知道那个切点的具体坐标了。
因为凸包的斜率是单调的,所以随着 \(K\) 的减小,切到的 \(x\) 也越大,所以可以二分这个 \(K\),我们可以根据切点的坐标去调整 \(K\) 直到切到 \((m,g(m))\) 为止,。


现在的问题就是怎么求最大截距,因为我们压根不知道这个凸包长什么样子。
会发现 \(b = g(x)-Kx\),定义 \(h(x) = g(x)-Kx\),如果我们能以较低的复杂度求出最大的 \(h(x)\) 以及此时的 \(x\),也就求出了我们要的东西。
考虑给 \(h(x)\) 定义一个合理的意义,不难发现他其实就是给每个物品多加了一个 \(-K\) 的权值(所以叫代权二分),选了这个数就要 \(-K\)。
而我们要求 \(h(x)\) 的最大值是没有限制要选多少个的,所以 dp 时直接 \(f_i = max_j(f_j + Val(j,i) - K)\) 即可,比一开始那个少了一维,会更好求,具体的优化方法/求法因题目而异,在例题中会讲。
注意最后的求 \(g(x)\) 时,要记得把 \(Kx\) 加上。

关于wqs二分的实现细节也在例题中。


例题

忘情

把式子变成 $((\sum_{i=1}^n x_i)+1)^2 $,设 \(S\) 为前缀和,那么朴素的 dp 是:
设 \(f[i][j]\) 表示前 \(i\) 个数划分成 \(j\) 段的最小值,转移为 \(f[i][j]=\min_{0<=k<i}(f[k][j-1] + (S_i - S_j + 1)^2)\)。
容易证明 \((a+b+1)^2 > (a+1)^2 + (b+1)^2\),也就是说分的段数越多答案越小,即按照上面的定义 \(g(x)\) 表示分 \(x\) 段的最小值,那么 \(g(x)\) 的图像应该是一个下凸壳:

二分一个斜率 \(K\),用斜率 \(K\) 的直线去切这个凸包,那么截距 \(b=h(x)=g(x)-Kx\),因为是下凸包,所以我们要求最小截距,即把一段的权值定义成 \(((\sum xi)+1)^2 - K\),然后去掉段数限制,求最小答案。
考虑对这个新的问题 \(dp\),设 \(dp[i]\) 表示前 \(i\) 个数的最小值,\(dp[i]=\min_{0<=j<i}(dp[j] + (S[i]-S[j]+1)^2 - K)\),因为还要求此时的横坐标 \(x\),所以还要额外记一个dp数组,转移也是显然的。
这是经典的斜率优化形式,可以用单调队列优化到 \(O(n)\),不会斜率优化的戳这里
总时间复杂度 \(O(n \log n)\)。

wqs 二分一些实现的细节:

  1. 这里因为是下凸包,所以斜率 \(K\) 是负的,但是为了方便二分时我们把他变成正的,所以 check 里 dp 变成 \(dp[i]=\min_{0<=j<i}(dp[j] + (S[i]-S[j]+1)^2 + K)\) , 原来二分要把斜率调大的就调小。
  2. 注意最后凸包可能会有一段斜率为 \(0\) 的线段,即可能 \(g(m-1)=g(m)\),那如果我们在 \(check\) 里的斜率优化dp,在 \(g\) 值相同时取的是靠左的点,那么二分写的就是: 如果返回的那个 \(x\) <=m,那就更新答案并把斜率调大(这里还认为斜率是负的,不进行 1. 的转换) ; 相反,如果我们在 \(check\) 里的斜率优化dp,在 \(g\) 值相同时取的是靠右的点,那么二分写的就是: 如果返回的那个 \(x\) >=m,那就更新答案并把斜率调小。看取的是靠左还是靠右只要看斜率优化维护凸包时写的是 >= 还是 >,> 就是取靠左的,>=就是取靠右的。

code

变量名稍有不同。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=1e5+5,inf=1e18;
inline int read(){
    int w = 1, s = 0;
    char c = getchar();
    for (; c < '0' || c > '9'; w *= (c == '-') ? -1 : 1, c = getchar());
    for (; c >= '0' && c <= '9'; s = 10 * s + (c - '0'), c = getchar());
    return s * w;
}

int n,m,a[N]; 
int dp[N],g[N];  //dp[i]表示前 i 个数分成若干段的最小值,g[i] 表示取到最小值分的段树 
int dq[N],l,r;
int calc(int j){  //求纵坐标 
	return dp[j]+a[j]*a[j]-2ll*a[j];
} 
void check(int mid){
	l=1,r=0;
	dp[0]=0,g[0]=0;   
	dq[++r]=0;  //放 0 不是 1,因为可以自成 1 段。 
	for(int i=1;i<=n;i++){
		while(l<r && ( calc(dq[l+1]) - calc(dq[l]) ) < (2ll * a[i] * (a[dq[l+1]] - a[dq[l]]))) l++;  //把开头斜率小于当前斜率的线段 pop 掉
		int j=dq[l];
		dp[i]=dp[j]+(a[i]-a[j]+1ll)*(a[i]-a[j]+1ll)+mid;
		g[i]=g[j]+1ll;
		while(l<r && ( calc(i) - calc(dq[r]) ) * ( a[dq[r]] - a[dq[r-1]]) < ( calc(dq[r]) - calc(dq[r-1] ) ) * ( a[i] - a[dq[r]] )) r--;  //维护凸壳
		dq[++r]=i; 
	}
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read(),a[i]+=a[i-1];
	int l=0,r=inf,mid,ans=0;   //实际上斜率是负的,但是移项之后:b=f(x)-kx,所以就干脆把 k 取成正的,这样在check里是每一段+mid,而不是-mid 
	while(l<=r){
		mid=(l+r)>>1;
		check(mid);
		if(g[n]<=m) r=mid-1,ans=mid;
		else l=mid+1; 
	}
	check(ans);
	printf("%lld\n",dp[n]-ans*m);  //这里要减掉 mid(也就是最后的 ans) 带来的贡献 
	return 0;
}

标签:二分,wqs,凸包,斜率,DP,Kx,dp
From: https://www.cnblogs.com/FloatingLife/p/18399206

相关文章

  • NOIP2024集训Day23 DP常见模型3 - 树形
    NOIP2024集训Day23DP常见模型3-树形A.[CSP-S2021]括号序列区间dp,令\(f_{l,r}\)表示从位置\(l\)到位置\(r\)一共的合法序列总情况数量。一共有六种不同的转移情况,所以将\(f_{l,r}\)扩充到三维。全是*(...)(...)**(...)***,左边以括号序列开头,右边以*结尾......
  • Modbus RTU转Profibus DP协议网关(推荐收藏呀!)
    ModbusRTU转ProfibusDP实现网络协议互通这一问题备受众人瞩目,而远创智控YC-MDPB-001可以轻松化解这一难题。接下来,作者将从该设备的主要功能、技术参数、性能优势以及配置方法等几个方面进行深入阐述。这款协议转化网关在工业自动化领域有着至关重要的地位,能够高效地转换不同......
  • 实现TCP收发信息和UDP收发信息
    1.TCP通信服务器端#include<myhead.h>#defineSERPORT6666#defineSERIP"192.168.0.136"#defineBACKLOG5intmain(intargc,constchar*argv[]){ intoldfd=socket(AF_INET,SOCK_STREAM,0); if(oldfd==-1) { perror("socket"); retu......
  • 每日OJ_牛客_最长递增子序列(dp/贪心模板)
    目录牛客_最长递增子序列(dp/贪心模板)解析代码牛客_最长递增子序列(dp/贪心模板)最长公共子序列__牛客网解析代码在一个序列中找最长递增子序列,动态规划的典型应用,下面是两个模版CISdp模板:#include<iostream>#include<vector>usingnamespacestd;intLIS(vect......
  • 概期DP做题记录
    概期DPP3600考虑\(ans\in[1,x]\),那么有:\[\begin{aligned}E(ans)&=\sum_{i\in[1,x]}iP(ans=i)\\&=\sum_{i\in[1,x]}P(ans\geqi)\\&=\sum1-P(ans<i)\\&=x-\sumP(ans<i)\end{aligned}\]我们就只需要计......
  • 二分查找:手拿把掐!------Java代码实现
    “没有天赋,那就不断重复.”文章目录前言文章有误敬请斧正不胜感恩!模板一:(最基本的)**左闭右闭:**[left,right]模板二:**左闭右开区间模板:**区间:左闭右开[left,right):模板三:开区间模板:(left,right)循环不变量:二分查找易错点:做题经验:疑问及解答:我自己的......
  • DP优化——斜率优化
    引言在学数据结构优化dp,单调队列优化dp时都很快就懂了,四边形不等式优化dp看一看也懂了,只有斜率优化理解了一个月还不懂,最后在其他大佬和资料的帮助下成功学懂了,于是争取这篇题解在以后又不会的时候一遍就懂。前置数学知识1.一次函数初中数学知识,见八年级数学课本。2.凸包(......
  • 子比主题美化 – 自助售卡/发卡插件 源码 | WordPress插件,完美支持
    插件功能支持自由添加卡密支持查看卡密库存邮箱自动发送卡密信息后台卡密库存不足提醒如何使用:在后台新建一篇文章,然后选择自动售卡。设置相关价格(不支持将价格设置为0)。移动到已编辑文章的底部(添加密码信息)直接发布文章以显示文章销售卡。安装方法:在Wordpress后......
  • NOIP2024集训Day21 DP常见模型2 - 背包
    NOIP2024集训Day21DP常见模型2-背包A.[BZOJ4987]Tree树形背包dp先考虑几个显而易见的性质:选出的点一定是相邻的对于选出的点,如果从\(a_k\)再走回\(a_1\),那么就相当于每条边经过了两次由于题目没有包含\(dis(a_k,a_1)\),因此就相当于选出的点中的一条链可以只......
  • NOIP2024集训Day20 DP常见模型1 - 序列
    NOIP2024集训Day20DP常见模型1-序列A.[JOI2022Final]Let'sWintheElection贪心+DP。首先,一定是所有协作者同时在同一个州演讲,这样才最优。然后,假设我们已经知道所有州的方案(支持、支持+协作、反对),那我们一定是先按照从小到大的顺序拿下所有“支持+协作”州,这样最优。......