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. Bzoj2832 / Bzoj3874 宅男小C

    Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 124  Solved: 26 Description 众所周知,小C是个宅男,所以他的每天的食物要靠外 ...

  2. 透彻理解Spring事务设计思想之手写实现(山东数漫江湖)

    前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),D ...

  3. B. Complete the Word(Codeforces Round #372 (Div. 2)) 尺取大法

    B. Complete the Word time limit per test 2 seconds memory limit per test 256 megabytes input standar ...

  4. vue中的表单异步校验方法封装

    在vue项目的开发中,表单的验证必不可少,在开发的过程中,用的是vue+iview的一套,我们知道iview的表单验证是基于async-validator,对于async-validator不熟悉的可 ...

  5. CSS浮动为什么不会遮盖同级元素

    1.问题描述 在W3CSchool学习web前端时,看完CSS定位-浮动这一节后,感觉没有什么问题.但是在CSS高级-分类这一节的中进行实践时,遇到了如下问题.测试地址:浮动的简单应用. 完整的htm ...

  6. mave的依赖范围

    compile(编译范围) compile是默认的范围:如果没有提供一个范围,那该依赖的范围就是编译范 围.编译范围依赖在所有的classpath中可用,同时它们也会被打包. provided(已提供 ...

  7. Django rest framework 的认证流程(源码分析)

    一.基本流程举例: urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/', views.HostView.as_view() ...

  8. 从LFS官方文档构建完整Linux系统

    从LFS官方文档构建完整Linux系统 http://www.cnblogs.com/sonofdark/p/4962609.html 这不是新手教程!!! Parallels Desktop (为防 ...

  9. keil问题:Error: failed to execute 'C:\Keil\ARM\BIN\ArmCC'

    1.打开cmd,进入相应的路径下 cd C:\Keil\ARM\BIN\ 输入 armcc  若显示如下界面则说明keil已经注册 2.若注册成功还没解决问题,则说明软件是在网上下载的破解版的,建议卸 ...

  10. [ python ] 查询数据库生成Excel并发送邮件

    开发要求: 由于管理后台导出数据非常缓慢,找程序员解决无果后,自己动手写了一个脚本,每天定时将报表发送给业务部门. 1. 通过条件查询MySQL获取数据       2. 将获取的数据写入到Excel ...