WQS二分/帶權二分/DP凸優化
模型:有 個物品,選擇每一個都會有相應的權值,需要求出強制選 個物品時的最大/最小權值和
參考博客:關於WQS二分算法以及其一個細節證明
大致思路:
限制個數的最大/最小權值和可以用 的 求出
根據 圖像,可以畫出 圖像,發現是上凸下凸包,斜率單調
設用直線 去切這個圖像,得到
使得截距 最大,即對於每個數的權值 後
求一遍任意個數情況下的最大最小 , 的 可以完成
注意事項:
若在答案附近出現斜率相同的情況
則二分判斷結果 , 爲儘可能少選的數值
二分判斷結果 , 爲儘可能多選的數值
權值 要根據實際意義轉化,有時可以爲
注意二分的結果是否爲目標值 的截距,最終答案要加上
例題一:洛谷P2619 [國家集訓隊2]Tree I
大意:無向帶權連通圖,每條邊爲白色或黑色,求一棵最小權的恰好有 條白色邊的生成樹
題解一:
給每條白色邊權
則隨着x的增大,最小生成樹裏白色邊的條數會減少,具有單調性
二分即可
題解二:
根據 二分,發現這個 是一個下凸包
二分斜率,每條白色邊權
對答案 即可
思考:
發現兩者的代碼除了邊權的處理外,幾乎一模一樣
其實題解一里也是枚舉斜率,相比於題解二,它枚舉的是斜率的相反數
因此在二分裏, 與 相反,兩種方法具有同樣的意義
#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n, m, k, f[maxn], ans, num;
struct edge {
int s, t, c, col;
} e[maxn], ed[maxn];
bool cmp(const edge A,const edge B) {
if(B.c == A.c) return A.col < B.col;
return A.c < B.c;
}
int getf(int x) {
return x == f[x] ? x : f[x] = getf(f[x]);
}
bool ck(int x) {
for(int i=1; i<=n; i++) f[i] = i;
for(int i=1; i<=m; i++) {
ed[i] = e[i];
if(!e[i].col) ed[i].c -= x;
}
sort(ed+1, ed+1+m, cmp);
ans = 0, num = 0;
for(int i=1; i<=m; i++) {
int fs = getf(ed[i].s), ft = getf(ed[i].t);
if(fs == ft) continue;
f[fs] = ft;
ans += ed[i].c;
num += !ed[i].col;
}
return num >= k;
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for(int i=1; i<=m; i++) {
scanf("%d%d%d%d", &e[i].s, &e[i].t, &e[i].c, &e[i].col);
e[i].s++, e[i].t++;
}
int l = -1e5, r = 1e5, mid;
while(l <= r) {
mid = l + r >> 1;
if(ck(mid)) r = mid - 1;
else l = mid + 1;
}
ck(l);
printf("%d\n", ans + k * l);
}
例題二:[BZOJ1150][CTSC2007]數據備份
大意: 個辦公樓排成一條直線,給出距離最開始的距離
選擇 對辦公樓,使得 對辦公樓每對之間的距離和最小
題解:
圖像上凸,設 爲前 個數,第 個沒選的最小代價
爲前 個數,第 個選擇的最小代價
注意儘可能少選與多選的區別
#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n, k, d[maxn];
ll dp[maxn][2], num[maxn][2];
int get(int x){
if(dp[x][0] == dp[x][1]) return num[x][1] < num[x][0];
return dp[x][1] < dp[x][0];
}
bool ck(ll x) {
dp[1][0] = num[1][0] = 0;
dp[1][1] = num[1][1] = 1e18;
for(int i=2; i<=n; i++){
dp[i][0] = dp[i-1][get(i-1)];
num[i][0] = num[i-1][get(i-1)];
dp[i][1] = dp[i-1][0] + d[i] - d[i-1] - x;
num[i][1] = num[i-1][0] + 1;
}
int getn = get(n);
dp[n][0] = dp[n][getn];
num[n][0] = num[n][getn];
return num[n][0] <= k;
}
int main() {
scanf("%d%d", &n, &k);
ll l = 0, r = 0, mid;
for(int i=1; i<=n; i++) scanf("%d", d+i), r += d[i];
while(l <= r){
mid = l + r >> 1;
if(ck(mid)) l = mid + 1;
else r = mid - 1;
}
ck(r);
printf("%lld\n", dp[n][0] + 1ll * k * r);
}