WQS二分题集
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 ;
}
这题的经典做法是可撤销贪心,但也可以用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 ;
}
同上题,设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 ;
}
同样先二分斜率去掉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 ;
}
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二分题集的更多相关文章
- 【CF739E】Gosha is hunting(WQS二分套WQS二分)
		
点此看题面 大致题意: 你有两种捕捉球(分别为\(A\)个和\(B\)个),要捕捉\(n\)个神奇宝贝,第\(i\)个神奇宝贝被第一种球捕捉的概率是\(s1_i\),被第二种球捕捉的概率是\(s2_i ...
 - ACM题集以及各种总结大全!
		
ACM题集以及各种总结大全! 虽然退役了,但是整理一下,供小弟小妹们以后切题方便一些,但由于近来考试太多,顾退役总结延迟一段时间再写!先写一下各种分类和题集,欢迎各位大牛路过指正. 一.ACM入门 关 ...
 - CF739E Gosha is hunting DP+wqs二分
		
我是从其他博客里看到这题的,上面说做法是wqs二分套wqs二分?但是我好懒呀,只用了一个wqs二分,于是\(O(nlog^2n)\)→\(O(n^2logn)\) 首先我们有一个\(O(n^3)\)的 ...
 - 关于WQS二分算法以及其一个细节证明
		
应用分析 它的作用就是题目给了一个选物品的限制条件,要求刚好选$m$个,让你最大化(最小化)权值, 然后其特点就是当选的物品越多的时候权值越大(越小). 算法分析 我们先不考虑物品限制条件, 假定我们 ...
 - [总结] wqs二分学习笔记
		
论文 提出问题 在某些题目中,强制规定只能选 \(k\) 个物品,选多少个和怎么选都会影响收益,问最优答案. 算法思想 对于上述描述的题目,大部分都可以通过枚举选择物品的个数做到 \(O(nk^2)\ ...
 - ACM题集以及各种总结大全(转)
		
ACM题集以及各种总结大全! 虽然退役了,但是整理一下,供小弟小妹们以后切题方便一些,但由于近来考试太多,顾退役总结延迟一段时间再写!先写一下各种分类和题集,欢迎各位大牛路过指正. 一.ACM入门 关 ...
 - [学习笔记]凸优化/WQS二分/带权二分
		
从一个题带入:[八省联考2018]林克卡特树lct——WQS二分 比较详细的: 题解 P4383 [[八省联考2018]林克卡特树lct] 简单总结和补充: 条件 凸函数,限制 方法: 二分斜率,找切 ...
 - [八省联考2018]林克卡特树lct——WQS二分
		
[八省联考2018]林克卡特树lct 一看这种题就不是lct... 除了直径好拿分,别的都难做. 所以必须转化 突破口在于:连“0”边 对于k=0,我们求直径 k=1,对于(p,q)一定是从p出发,走 ...
 - 「学习笔记」wqs二分/dp凸优化
		
[学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...
 
随机推荐
- 省队集训Day1 过河
			
[题目大意] 小奇特别喜欢猪,于是他养了$n$只可爱的猪,但这些猪被魔法猪教会了魔法,一不看着某些猪就会自己打起来. 小奇要带着他的猪讨伐战狂,路途中遇到了一条河.小奇找到了一条船,可惜这条船一次只能 ...
 - NYOJ   1237   最大岛屿    (深搜)
			
题目链接 描述 神秘的海洋,惊险的探险之路,打捞海底宝藏,激烈的海战,海盗劫富等等.加勒比海盗,你知道吧?杰克船长驾驶着自己的的战船黑珍珠1号要征服各个海岛的海盜,最后成为海盗王. 这是一个由海洋. ...
 - TensorFlow 模型保存和导入、加载
			
在TensorFlow中,保存模型与加载模型所用到的是tf.train.Saver()这个类.我们一般的想法就是,保存模型之后,在另外的文件中重新将模型导入,我可以利用模型中的operation和va ...
 - 爬虫--BeautifulSoup
			
什么是BeautifulSoup? BeautifulSoup支持的一些解析库 基本使用 from bs4 import BeautifulSoup html =""" ...
 - Perl中文件读取操作
			
Perl中文件读取操作 http://blog.csdn.net/yangxuan12580/article/details/51506216
 - ICTPOS3.0 词性标注集
			
Ag 形语素 形容词性语素.形容词代码为a,语素代码g前面置以A. a 形容词 取英语形容词adjective的第1个字母. ad 副形词 直接作状语的形容词.形容词代码a和副词代码d并在一起. an ...
 - C后端设计开发 - 第4章-武技-常见轮子下三路
			
正文 第4章-武技-常见轮子下三路 后记 如果有错误, 欢迎指正. 有好的补充, 和疑问欢迎交流, 一块提高. 在此谢谢大家了. Moonlight Shadow 纪念那个我爱的, 被我感动的女孩 ...
 - 封装构造函数,用canvas写饼状图和柱状图
			
封装构造函数,用canvas写饼状图和柱状图 封装函数 // 场景 function XDLScence( options ) { this.stage = options.stage; //执行场景 ...
 - [hadoop][基本原理]zookeeper场景使用
			
代码:https://github.com/xufeng79x/ZkClientTest 1. 简介 zookeeper的特性决定他适用到某些场景非常合适,比如典型的应用场景: 1.集群管理(Grou ...
 - .NET直接编译成本地代码:.NET Native架构简介
			
原文地址:http://blog.csdn.net/atfield/article/details/23449089