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. 五分钟学习Java8的流编程

    1.概述 Java8中在Collection中增加了一个stream()方法,该方法返回一个Stream类型.我们就是用该Stream来进行流编程的: 流与集合不同,流是只有在按需计算的,而集合是已经 ...

  2. Fire! (双bfs+预处理)

    题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem ...

  3. 关于win7局域网共享的相关设置

    模式1> 被访问方相关设置步骤: (1)被共享方的电脑开通来宾用户 (2)被共享方的电脑的本地安全策略需要设置成 "仅来宾" (3)被共享方的电脑高级共享设置中 " ...

  4. Android控件——ToggleButton多状态按钮(实现灯泡的开关)

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAxoAAAFxCAIAAAB7jkm1AAAgAElEQVR4nOy9eXgUVb7/Dy7j3BnH8T

  5. perl6 修改文件并覆盖

    use v6; my $filename = 'data.txt'; my $data = slurp $filename; say $data; $data ~~ s/'4'/'ABC'/; say ...

  6. mssql手工注入2

    --+ 先说一些函数的说明: substring(str,start,len) 截取字符串的作用,第一个参数为要截取的字符串,第二个参数为从哪里开始截取,第三个参数为截取的长度 ascii(char) ...

  7. 实战手工注入某站,mssql注入

    昨天就搞下来的,但是是工具搞得,为了比赛还是抛弃一阵子的工具吧.内容相对简单,可掠过. 报错得到sql语句: DataSet ds2 = BusinessLibrary.classHelper.Get ...

  8. Linux System.map文件【转】

    转自:http://blog.csdn.net/ysbj123/article/details/51233618 当运行GNU链接器gld(ld)时若使用了"-M"选项,或者使用n ...

  9. Visual Studio 附加到进程调试

    Visual Studio 果然是强大的,今天第一次使用附加到进程调试的功能!但是,在使用的时候发现进不了断点... 解决方案: 1.发布的时候选择Debug,而不是Release: 2.右键项目-& ...

  10. google fcm 推送的流程

    总结:1.给一个人推,能成功,2.给多个人推,有两种,一种是给组推,一种是给主题推,之前用的是组推,但是不成功,这里换成主题推: <?phpnamespace App\Http\Controll ...