WQS二分

WQS二分/帶權二分/DP凸優化

    模型:有 nn 個物品,選擇每一個都會有相應的權值,需要求出強制選 kk 個物品時的最大/最小權值和

    參考博客:關於WQS二分算法以及其一個細節證明
    大致思路:
    限制個數的最大/最小權值和可以用 n2n^2DPDP 求出
    根據 DPDP 圖像,可以畫出 xg(x)x - g(x) 圖像,發現是上凸//下凸包,斜率單調
    設用直線 y=kx+by = kx + b 去切這個圖像,得到 f(x)=b=g(x)kxf(x) = b = g(x) - kx
    使得截距 bb 最大,即對於每個數的權值 h(x)=kh(x) -= k
    求一遍任意個數情況下的最大//最小 f(x)f(x)O(n)O(n)DPDP 可以完成

  注意事項:

    ①:若在答案附近出現斜率相同的情況
    則二分判斷結果 num<=knum <= knumnum 爲儘可能少選的數值
    二分判斷結果 num>=knum >= knumnum 爲儘可能多選的數值
    ②:權值 h(x)=kh(x) -= k 要根據實際意義轉化,有時可以爲 h(x)+=kh(x) += k
    ③:注意二分的結果是否爲目標值 kk 的截距,最終答案要加上 kmidk * mid


例題一:洛谷P2619 [國家集訓隊2]Tree I

    大意:無向帶權連通圖,每條邊爲白色或黑色,求一棵最小權的恰好有 needneed 條白色邊的生成樹

    題解一:
    給每條白色邊權 +=x+= x
    則隨着x的增大,最小生成樹裏白色邊的條數會減少,具有單調性
    二分即可

    題解二:
    根據 WQSWQS 二分,發現這個 xg(x)x - g(x) 是一個下凸包
    二分斜率,每條白色邊權 =x-= x
    對答案 +kmid+ k * mid 即可

    思考:
    發現兩者的代碼除了邊權的處理外,幾乎一模一樣
    其實題解一里也是枚舉斜率,相比於題解二,它枚舉的是斜率的相反數
    因此在二分裏,llrr 相反,兩種方法具有同樣的意義

#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]數據備份

    大意:nn 個辦公樓排成一條直線,給出距離最開始的距離
    選擇 kk 對辦公樓,使得 kk 對辦公樓每對之間的距離和最小

    題解:
    圖像上凸,設 dp[i][0]dp[i][0] 爲前 ii 個數,第 ii 個沒選的最小代價
    dp[i][1]dp[i][1] 爲前 ii 個數,第 ii 個選擇的最小代價
    注意儘可能少選與多選的區別

#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);
}
發佈了231 篇原創文章 · 獲贊 226 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章