引入
一般问题:用于解决 n 个物品,恰好选 m 个 ,最大化或最小化权值和
用 wqs 的题目特点
1、题目可以用这样的 dp 求解 :dp[i][j] 表示 前 i 个 选 j 个的最优策略,
dp[i][j]=max/min { dp[k][j-1] + cost(k , i) } ,复杂度不管怎么优化都为平方级别
2、若没有恰好选 m 个的限制会很容易用 dp 求解
3、设 f(i) 为选 i 个物品的最佳答案,函数 f 的图像为一个凸包
tips:做题时可感性理解直接尝试,毕竟码量还好
思路
对于 wqs 二分我个人采用两种同时理解,一种方便理解,一种方便写代码
先尝试理解,已知选 i 个时的最优值 f(i) 构成凸包。假设他是一个上凸包。
横坐标为选 x 个,纵坐标为选 x 个时最优值,显然答案为 f(m) ,但我们得不到这个凸包
考虑用一条斜率为 k 的直线去切这个凸包(二分这个 k ),发现
1、当这条直线与凸包相切时,可以得到选 不知道多少个 时的最优值
2、对于所有斜率为 k 且与凸包有交点的直线,其截距是最大的
对于第一点,意味着我们可以通过调整 k 来切到点( m , f(m) )
对于第二点,意为着我们可以得到这条切线,怎么做呢?
假设斜率为k的凸包的切线为 y = kx + b ,由于 b = y - kx ,所以
对于斜率为k 且与凸包有交点的直线的截距 b ,有 b(x) = f(x) - kx
发现这个柿子等价于把每次选择时的权值和先 -=k ,此时就没有 恰好 m 的限制了
用一个线性的 dp 可同时知道 b 的最值和此时 x 的值 ,然后返回判断二分
写代码时可以理解为
给每一次选择(可能选一个也可能很多个,看题目)一个附加权值,将每次操作(一般是每个点)点权减这个权值,然后就可以不管限制个数,直接做,最后把答案加上 m * 附加权值
例题分析
考虑暴力dp, 𝑓[𝑖][𝑗][0/1] 为前 i 个房子,连了 j 条线,并且第 i 个房子是否和第 i−1 个房子连线。
转移很简单, 𝑓[𝑖][𝑗][0]=min(𝑓[𝑖−1][𝑗][0],𝑓[𝑖−1][𝑗][1]) ,𝑓[𝑖][𝑗][1]=𝑓[𝑖−1][𝑗−1][0]+x[𝑖]−x[𝑖−1]
发现空间爆炸
设 f[x] 为连 x 条线的最优答案,易发现为上凸包,考虑 wqs 二分 ,枚举一个附加权值,即可以去掉 j 这一维,就做完了。
代码
#include<bits/stdc++.h>
using namespace std;
#define N 101000
#define int long long
int f[N][2],g[N][2],n,k,x[N],ans;
#define inf 1LL<<60
void lc(int &x,int &y,int xx,int yy){
if(x>xx||(x==xx&&y>yy)) x=xx,y=yy;
}
void solv(int mid){
f[1][0]=g[1][0]=0;
f[1][1]=inf;
for(int i=2;i<=n;i++){
g[i][1]=g[i-1][0]+1,f[i][0]=f[i-1][0],g[i][0]=g[i-1][0];
lc(f[i][0],g[i][0],f[i-1][1],g[i-1][1]);
f[i][1]=f[i-1][0]+x[i]-x[i-1]-mid;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>x[i];
}
int l=0,r=1e9+1;
while(l<r){
int mid=(l+r)>>1;
int a1=inf,a2=0;
solv(mid);
lc(a1,a2,f[n][0],g[n][0]);
lc(a1,a2,f[n][1],g[n][1]);
if(a2>k) r=mid;
else{
ans=a1+mid*k,l=mid+1;
}
}
cout<<ans;
return 0;
}
下周考完whk继续写......