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三点共线的情况,这时如果当前二分的直线正好与这三点平行。这是我们要保证它返回的切点一定在我们当前枚举的二分区间之内。具体到这道题就是通过给等长的边按颜色排序控制最终收益相同的方案中白边的个数。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=l; i<=r; i++) 4 typedef long long ll; 5 using namespace std; 6 7 const int N=100100; 8 int n,m,cnt,tot,k,ans,u[N],v[N],w[N],c[N],fa[N]; 9 struct E{ int u,v,w,c; }e[N]; 10 11 bool operator<(E a,E b){ return a.w==b.w ? a.c>b.c : a.w<b.w; } 12 int find(int x){ return x==fa[x] ? x : fa[x]=find(fa[x]); } 13 14 bool check(int x){ 15 tot=cnt=0; 16 rep(i,1,n) fa[i]=i; 17 rep(i,1,m){ 18 e[i].u=u[i]; e[i].v=v[i]; e[i].w=w[i]; e[i].c=c[i]; 19 if (!c[i]) e[i].w-=x; 20 } 21 sort(e+1,e+m+1); 22 rep(i,1,m){ 23 int p=find(e[i].u),q=find(e[i].v); 24 if (p!=q){ 25 fa[p]=q; tot+=e[i].w; 26 if (!e[i].c) cnt++; 27 } 28 } 29 return cnt<=k; 30 } 31 32 int main(){ 33 freopen("bzoj2654.in","r",stdin); 34 freopen("bzoj2654.out","w",stdout); 35 scanf("%d%d%d",&n,&m,&k); 36 rep(i,1,m) scanf("%d%d%d%d",&u[i],&v[i],&w[i],&c[i]),u[i]++,v[i]++; 37 int L=-105,R=105; 38 while(L<=R){ 39 int mid=(L+R)>>1; 40 if (check(mid)) L=mid+1,ans=tot+k*mid; else R=mid-1; 41 } 42 printf("%d\n",ans); 43 return 0; 44 }