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. HDU5852 Intersection is not allowed!

    There are K pieces on the chessboard. The size of the chessboard is N*N. The pieces are initially pl ...

  2. 【51NOD-0】1012 最小公倍数LCM

    [算法]欧几里德算法 #include<cstdio> int gcd(int a,int b) {?a:gcd(b,a%b);} int main() { int a,b; scanf( ...

  3. 基本控件文档-UISlider属性---iOS-Apple苹果官方文档翻译

    本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址  //转载请注明出处--本文永久链接:http://www.cnblogs.com/C ...

  4. Tunnel Warfare(HDU1540+线段树+区间合并)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1540 题目: 题意:总共有n个村庄,有q次操作,每次操作分为摧毁一座村庄,修复一座村庄,和查询与询问的 ...

  5. 数据库===轻量级mysql数据库管理工具

    已经上传至: https://download.csdn.net/download/bo_mask/10276952

  6. python基础===中文手册,可查询各个模块

    http://python.usyiyi.cn/translate/python_352/index.html

  7. 005 JAVA多线程和并发基础面试问答(转载)

    原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ 多线程和并发问题是Ja ...

  8. 动画基础--基于Core Animation(2)

    参考:https://zsisme.gitbooks.io/ios-/content/ 前面的文章动画基础--基于Core Animation(1)提到了图层的基本概念以及可动画参数几何学等知识. 本 ...

  9. 解决Ubuntu的错误提示

    如果你是一个Ubuntu用户,也许偶尔甚至经常,遇到这样一个错误提示“System Program problem detected”. Ubuntu有一个内建的实用程序叫做Apport, 当一个程序 ...

  10. JVM核心机制(类加载器、三种类加载器、代理加载模式、双亲委派机制