WQS二分,一种优化一类特殊DP的方法。

很多最优化问题都是形如“一堆物品,取与不取之间有限制。现在规定只取k个,最大/小化总收益”。

这类问题最自然的想法是:设f[i][j]表示前i个取j个的最大收益,转移即可。复杂度O(n^2)。

那么,如果在某些情况下,可以通过将问题稍作转化,变成一个不强制选k个的DP,而最后DP出来的最优解一定正好选了k个,那么问题就会简化很多。

WQS二分就是基于这个思想。

首先考虑建一个二维坐标系,x轴是选的数的个数,y轴是最大收益,如果这个x-y图像有凸性,那么就可能通过给每个被选的数一个偏差值,将复杂度中的一个n变成log。因此,WQS二分又叫作凸优化/带权二分。

来看一个题:[BZOJ2654]Tree

按照上面所说建立坐标系,发现x-y图像的斜率单调递增。是一个下凸函数。

我们考虑给每一条白边减去某个值(一些地方是加上某个值,本质是一样的)cost,那么如果最终解选了x条边,则得到的值为实际值-cost*x。考虑这个式子的几何意义,就相当于将凸包通过斜率为cost的直线投影到y轴上。

可以发现,如果合适的选取cost值,可以使凸包上横坐标为k的这个投影后的纵坐标最大,这时就可以直接得出这个点的值了。

我们二分cost,于是问题转化为,求一棵每条白边都减去cost的图中的最小生成树,直接求MST即可。

每次根据哪个点投影后的纵坐标最大调整二分边界,这个类似于用一条直线去切这个凸包,根据切点横坐标调整。

这里需要注意一个问题,可能会存在k-1,k,k+1三点共线的情况,这时如果当前二分的直线正好与这三点平行。这是我们要保证它返回的切点一定在我们当前枚举的二分区间之内。具体到这道题就是通过给等长的边按颜色排序控制最终收益相同的方案中白边的个数。

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=l; i<=r; i++)
typedef long long ll;
using namespace std; const int N=;
int n,m,cnt,tot,k,ans,u[N],v[N],w[N],c[N],fa[N];
struct E{ int u,v,w,c; }e[N]; bool operator<(E a,E b){ return a.w==b.w ? a.c>b.c : a.w<b.w; }
int find(int x){ return x==fa[x] ? x : fa[x]=find(fa[x]); } bool check(int x){
tot=cnt=;
rep(i,,n) fa[i]=i;
rep(i,,m){
e[i].u=u[i]; e[i].v=v[i]; e[i].w=w[i]; e[i].c=c[i];
if (!c[i]) e[i].w-=x;
}
sort(e+,e+m+);
rep(i,,m){
int p=find(e[i].u),q=find(e[i].v);
if (p!=q){
fa[p]=q; tot+=e[i].w;
if (!e[i].c) cnt++;
}
}
return cnt<=k;
} int main(){
freopen("bzoj2654.in","r",stdin);
freopen("bzoj2654.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
rep(i,,m) scanf("%d%d%d%d",&u[i],&v[i],&w[i],&c[i]),u[i]++,v[i]++;
int L=-,R=;
while(L<=R){
int mid=(L+R)>>;
if (check(mid)) L=mid+,ans=tot+k*mid; else R=mid-;
}
printf("%d\n",ans);
return ;
}

再看一题:[BZOJ1150][CTSC2007]数据备份

这题的经典做法是可撤销贪心,但也可以用WQS做。

首先同样建出坐标系,发现是一个斜率单增的上凸包。先二分斜率去掉只选K个的限制,问题简化成普通DP。

f[i][0/1]表示前i个数,第i个数选了/没选,的最小代价。

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
typedef long long ll;
using namespace std; const int N=;
const ll inf=1e15;
int n,k,s[N];
ll L,R,ans;
struct P{ ll v,x; }f[N][];
P min(P a,P b){ if (a.v<b.v || (a.v==b.v && a.x<b.x)) return a; else return b; } bool jud(ll cost){
memset(f,0x7f,sizeof(f));
f[][]=(P){,};
rep(i,,n){
f[i][]=min(f[i-][],f[i-][]);
f[i][]=(P){f[i-][].v+s[i]-s[i-]-cost,f[i-][].x+};
}
f[n][]=min(f[n][],f[n][]);
if (f[n][].x<=k) { ans=f[n][].v+k*cost; return ; } else return ;
} int main(){
freopen("bzoj1150.in","r",stdin);
freopen("bzoj1150.out","w",stdout);
scanf("%d%d",&n,&k);
rep(i,,n) scanf("%d",&s[i]),R+=s[i];
while (L<=R){
ll mid=(L+R)>>;
if (jud(mid)) L=mid+; else R=mid-;
}
printf("%lld\n",ans);
return ;
}

[BZOJ2151]种树

同上题,设f[i][0/1][0/1]表示前i个数,第一个数选了/没选,第i个数选了/没选,的最大收益。

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,inf=1e9;
int n,m,ans,a[N],L,R;
struct P{ int v,x; }f[N][][];
P max(P a,P b){ if (a.v>b.v || (a.v==b.v && a.x<b.x)) return a; else return b; }
P add(P s,int b){ return (P){s.v+b,s.x+}; } bool jud(int cost){
memset(f,,sizeof(f));
f[][][]=(P){a[]-cost,}; f[][][]=f[][][]=(P){-inf,};
rep(i,,n){
f[i][][]=max(f[i-][][],f[i-][][]);
f[i][][]=max(f[i-][][],f[i-][][]);
f[i][][]=add(f[i-][][],a[i]-cost);
f[i][][]=add(f[i-][][],a[i]-cost);
}
P s=max(max(f[n][][],f[n][][]),f[n][][]);
if (s.x<=m) { ans=s.v+m*cost; return ; } else return ;
} int main(){
freopen("bzoj2151.in","r",stdin);
freopen("bzoj2151.out","w",stdout);
scanf("%d%d",&n,&m);
if (m>n/) { puts("Error!"); return ; }
rep(i,,n) scanf("%d",&a[i]);
L=-; R=;
while (L<=R){
int mid=(L+R)>>;
if (jud(mid)) R=mid-; else L=mid+;
}
printf("%d\n",ans);
return ;
}

[BZOJ5311]贞鱼

同样先二分斜率去掉K的限制,问题变为求最小冲突。

f[i]表示前i个人的最小冲突,s[i][j]表示冲突表的二维前缀和,则有f[i]=max{f[j]+(s[i][i]-s[i][j]-s[j][i]+2*s[j][j])/2}。

同时这个DP是有决策单调性的,于是问题就由O(n^2*k)优化到了O(nlognlogk)。

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
int n,k,st,ed,s[N][N],f[N],g[N];
struct P{ int x,l,r; }q[N]; int rd(){
int x=; char ch=getchar();
while (ch<'' || ch>'') ch=getchar();
while (ch>='' && ch<='') x=(x<<)+(x<<)+(ch^),ch=getchar();
return x;
} int cal(int j,int i){ return f[j]+((s[i][i]-s[i][j]-s[j][i]+s[j][j])>>); } bool chk(int i,int j,int k){
int x=cal(i,k),y=cal(j,k);
return (x<y) || (x==y && g[i]<g[j]);
} int find(int i,int j){
int l=q[ed].l,r=n,res=;
while (l<=r){
int mid=(l+r)>>;
if (chk(i,j,mid)) res=mid,r=mid-; else l=mid+;
}
return res;
} void solve(int c){
st=ed=; q[]=(P){,,n};
rep(i,,n){
++q[st].l; if (q[st].l>q[st].r) st++;
f[i]=cal(q[st].x,i)-c; g[i]=g[q[st].x]+;
if (st>ed || chk(i,q[ed].x,n)){
while (st<=ed && chk(i,q[ed].x,q[ed].l)) ed--;
if (st>ed) q[++ed]=(P){i,i,n};
else{
int x=find(i,q[ed].x);
q[ed].r=x-; q[++ed]=(P){i,x,n};
}
}
}
} int main(){
freopen("bzoj5311.in","r",stdin);
freopen("bzoj5311.out","w",stdout);
scanf("%d%d",&n,&k);
rep(i,,n) rep(j,,n) s[i][j]=s[i-][j]+s[i][j-]-s[i-][j-]+rd();
int l=-s[n][n],r=,res=;
while (l<=r){
int mid=(l+r)>>; solve(mid);
if (g[n]<=k) res=mid,l=mid+; else r=mid-;
}
solve(res); printf("%d\n",f[n]+k*res);
return ;
}

[BZOJ5252]林克卡特树

https://www.cnblogs.com/HocRiser/p/9055203.html

 #include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=l; i<=r; i++)
typedef long long ll;
using namespace std; const int N=;
int n,k,u,v,w,cnt,to[N<<],nxt[N<<],val[N<<],h[N];
ll mid,tot;
void add(int u,int v,int w){ to[++cnt]=v; val[cnt]=w; nxt[cnt]=h[u]; h[u]=cnt; }
struct P{
ll x,y;
bool operator < (const P &b) const {return x==b.x? y>b.y : x<b.x;}
P operator + (const P &b) const {return (P){x+b.x,y+b.y};}
P operator + (int b) {return (P){x+b,y};}
}dp[][N];
P upd(P a){ return (P){a.x-mid,a.y+}; } void dfs(int u,int fa){
dp[][u]=max(dp[][u],(P){-mid,});
for (int i=h[u],v; i; i=nxt[i])
if ((v=to[i])!=fa){
dfs(v,u);
dp[][u]=max(dp[][u]+dp[][v],upd(dp[][u]+dp[][v]+val[i]));
dp[][u]=max(dp[][u]+dp[][v],dp[][u]+dp[][v]+val[i]);
dp[][u]=dp[][u]+dp[][v];
}
dp[][u]=max(dp[][u],max(upd(dp[][u]),dp[][u]));
} int main(){
freopen("lct.in","r",stdin);
freopen("lct.out","w",stdout);
scanf("%d%d",&n,&k); k++;
rep(i,,n) scanf("%d%d%d",&u,&v,&w),tot+=abs(w),add(u,v,w),add(v,u,w);
ll L=-tot,R=tot;
while (L<=R){
mid=(L+R)>>; memset(dp,,sizeof(dp)); dfs(,);
if (dp[][].y<=k) R=mid-; else L=mid+;
}
memset(dp,,sizeof(dp)); mid=L; dfs(,); printf("%lld\n",L*k+dp[][].x);
return ;
}

WQS二分的另外两个题:CF958E2,CF739E

WQS二分题集的更多相关文章

  1. 【CF739E】Gosha is hunting(WQS二分套WQS二分)

    点此看题面 大致题意: 你有两种捕捉球(分别为\(A\)个和\(B\)个),要捕捉\(n\)个神奇宝贝,第\(i\)个神奇宝贝被第一种球捕捉的概率是\(s1_i\),被第二种球捕捉的概率是\(s2_i ...

  2. ACM题集以及各种总结大全!

    ACM题集以及各种总结大全! 虽然退役了,但是整理一下,供小弟小妹们以后切题方便一些,但由于近来考试太多,顾退役总结延迟一段时间再写!先写一下各种分类和题集,欢迎各位大牛路过指正. 一.ACM入门 关 ...

  3. CF739E Gosha is hunting DP+wqs二分

    我是从其他博客里看到这题的,上面说做法是wqs二分套wqs二分?但是我好懒呀,只用了一个wqs二分,于是\(O(nlog^2n)\)→\(O(n^2logn)\) 首先我们有一个\(O(n^3)\)的 ...

  4. 关于WQS二分算法以及其一个细节证明

    应用分析 它的作用就是题目给了一个选物品的限制条件,要求刚好选$m$个,让你最大化(最小化)权值, 然后其特点就是当选的物品越多的时候权值越大(越小). 算法分析 我们先不考虑物品限制条件, 假定我们 ...

  5. [总结] wqs二分学习笔记

    论文 提出问题 在某些题目中,强制规定只能选 \(k\) 个物品,选多少个和怎么选都会影响收益,问最优答案. 算法思想 对于上述描述的题目,大部分都可以通过枚举选择物品的个数做到 \(O(nk^2)\ ...

  6. ACM题集以及各种总结大全(转)

    ACM题集以及各种总结大全! 虽然退役了,但是整理一下,供小弟小妹们以后切题方便一些,但由于近来考试太多,顾退役总结延迟一段时间再写!先写一下各种分类和题集,欢迎各位大牛路过指正. 一.ACM入门 关 ...

  7. [学习笔记]凸优化/WQS二分/带权二分

    从一个题带入:[八省联考2018]林克卡特树lct——WQS二分 比较详细的: 题解 P4383 [[八省联考2018]林克卡特树lct] 简单总结和补充: 条件 凸函数,限制 方法: 二分斜率,找切 ...

  8. [八省联考2018]林克卡特树lct——WQS二分

    [八省联考2018]林克卡特树lct 一看这种题就不是lct... 除了直径好拿分,别的都难做. 所以必须转化 突破口在于:连“0”边 对于k=0,我们求直径 k=1,对于(p,q)一定是从p出发,走 ...

  9. 「学习笔记」wqs二分/dp凸优化

    [学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...

随机推荐

  1. 微信公众号支付开发全过程(Java 版)

    一.微信官方文档微信支付开发流程(公众号支付) 首先我们到微信支付的官方文档的开发步骤部分查看一下需要的设置. [图片上传失败...(image-5eb825-1531014079742)] 因为微信 ...

  2. java map 转 json 自编封装

    1.自编封装代码: import com.alibaba.fastjson.JSON; import java.util.*; public class jsonConversion { privat ...

  3. 21、python操作redis的模块?

    什么是redis? redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(s ...

  4. linux编程之文件操作

    在linux下用文件描述符来表示设备文件盒普通文件,文件描述符是一个整型的数据,所有对文件的操作都是通过文件描述符来实现的. 文件描述符是文件系统中连接用户空间和内核空间的枢纽,当我们打开一个或者创建 ...

  5. CiteSeer统计的计算机领域的期刊和会议的影响因子(2005)

    产生自CiterSeer 2005数据库,实际的影响因子可能更高.仅供参考使用.真实的IF还需去官网查看 . OSDI: 3.31 (top 0.08%) . USENIX Symposium on ...

  6. gpio子系统和pinctrl子系统(中)

    pinctrl子系统核心实现分析 pinctrl子系统的内容在drivers/pinctrl文件夹下,主要文件有(建议先看看pinctrl内核文档Documentation/pinctrl.txt): ...

  7. Laravel 调试器 Debugbar 和数据库导出利器 DbExporter 扩展安装及注意事项

    一.Debugbar安装 参考:Laravel 调试利器 —— Laravel Debugbar 扩展包安装及使用教程 的“2.安装”部分 二.DbExporter安装 参考:Laravel 扩展推荐 ...

  8. 网络知识===wireshark抓包数据分析(一)

    wireshark分析: 上图是我进行一个HTTP协议的下载,文件内容大概是1.7M左右. 抓包数据: https://files.cnblogs.com/files/botoo/wireshark% ...

  9. HDU 5118 GRE Words Once More!

    题目链接:HDU-5118 题意:给定一个有向无环图,每条边有一个权值.标定一些特定节点为“特殊节点”.从节点1出发到某“特殊节点”结束的路径,称为一个“GRE单词”.单词由路径上的权值组成.给定一组 ...

  10. C# 笔记——覆盖和重写

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Cons ...