WQS二分(凸完全单调性优化dp)
wqs二分是巨佬王钦石在2012年论文中提出的一种二分方法。或者叫做带权二分。或者叫dp凸优化,一般用于
- 复杂度一般都是
及以上,不能接受。 - 如果把这个限制
的条件去了那就很水。
其实主要是你觉得这个题能用那就能用(
既然叫凸优化,那么它就只使用于答案构成一个凸包的问题。首先手搓一个题举例子。
个物品,每个都有权值,以某种方式计算权值,要求权值最大,选的方式和选哪些物品都会影响权值。
首先我们有一个相当显然的dp:设
于是这个是
如图,我们有这样一个凸包。
然后我们用一条直线来切这个凸包,直到切到我们想要的
大体过程就是这样,每次二分判断当前答案是否合法(就是
假如说这条直线是
然后是注意事项。非常重要。
二分最后一定要减去
,而不是你最后check出的答案 乘 或直接不重新跑一遍斜率直接输出答案。
为什么?考虑这样一种情况:
你的一条直线同时切到了一堆点。而如果你
实际上,我们不需要精确得到我们最后的斜率,因为最后减掉了。我们可以在每次
然后上题:Tree I
题意:
直接套路wqs二分。每次二分一个权值给白边加上,然后最小生成树,最后回代一下斜率就行了。然后,这个题在黑白边相同的时候,你有两种选择:选黑或白。这时(还是用上面那张图):
如果你选白边,那么最后切出来的点是
的,要在 时转移答案。
如果你选黑边,那么最后切出来的点是的,要在 时转移答案。
int main(){
scanf("%d%d%d",&n,&m,&nd);
for(int i=1;i<=m;i++){
int u,v,w,col;scanf("%d%d%d%d",&u,&v,&w,&col);
edge[i]={u,v,w,col};
if(col==0)swap(edge[++cnt],edge[i]);
}
sort(edge+1,edge+cnt+1);sort(edge+cnt+1,edge+m+1);
int l=-100,r=100;//注意因为这个题斜率有负数所以要-100
while(l<r){
int mid=(l+r+1)>>1,ans=0,i=1,j=cnt+1;
for(int i=0;i<n;i++)fa[i]=i;
for(i=1,j=cnt+1;i<=cnt&&j<=m;){
edge[i].w+mid<=edge[j].w?ans+=add(i++):add(j++);//优先选白边加入
}
while(i<=cnt)ans+=add(i++);
if(ans<nd)r=mid-1;
else l=mid;
}
int mid=l;//最后回代斜率
for(int i=0;i<n;i++)fa[i]=i;
int ans=0,i=1,j=cnt+1;
while(i<=cnt&&j<=m){
if(edge[i].w+mid<=edge[j].w)ans+=(edge[i].w+mid)*add(i),i++;
else ans+=edge[j].w*add(j),j++;
}
while(i<=cnt)ans+=(edge[i].w+mid)*add(i),i++;
while(j<=m)ans+=edge[j].w*add(j),j++;
printf("%d",ans-nd*mid);
}
忘情
题意:
首先如果没有这个
int y(int x){
return dp[x]+s[x]*s[x]-2*s[x];
}
double slope(int x1,int x2){
return 1.0*(y(x2)-y(x1))/(1.0*s[x2]-s[x1]);
}
bool check(int val){
int l=1,r=1;
for(int i=1;i<=n;i++){
while(l<r&&slope(q[l],q[l+1])<=2*s[i])l++;
dp[i]=dp[q[l]]+(s[i]-s[q[l]]+1)*(s[i]-s[q[l]]+1)+val;
cnt[i]=cnt[q[l]]+1;
while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i))r--;
q[++r]=i;
}//斜率优化板子
return cnt[n]>=m;//我是在>=m时记录答案的 因为斜率优化的时候记录的是当前斜率最大的数 可能比m大所以要>=m
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
int a;scanf("%lld",&a);
s[i]=s[i-1]+a;
}
int l=0,r=1e15,ans;
while(l<r){
int mid=(l+r)>>1;
if(check(mid))l=mid+1,ans=mid;
//这个题求最小值 是上凸包 如果答案超过m就记录顺便增大斜率来使切点横坐标减小 反之增大
else r=mid;
}
check(ans);
printf("%lld",dp[n]-m*ans);
return 0;
}
后记:这个东西是我在写这篇博客顺便给joke3579调代码的时候突然“我逐渐理解一切”的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!