1. 程式人生 > 實用技巧 >[APIO2015]八鄰旁之橋

[APIO2015]八鄰旁之橋

注意到 \(K = 1, 2\) 於是我們可以從簡單的 \(K = 1\) 開始入手。可以發現家和辦公室在同一邊的人不管建不建橋都是無所謂的,因此下面我們只需要考慮不在同一邊的人,假設橋的位置在 \(d\) 那麼答案可以簡單的表示為:

\[\sum\limits_{i = 1} ^ n |S_i - d| + |T_i - d| \]

這可以看作是 \(2n\) 個在數軸上的點到 \(d\) 的距離,這是一個很經典的問題,只需取這 \(2n\) 個點的中位數即可。

接下來再考慮 \(K = 2\) 的情況,如果我們想直接求出兩座橋的位置是很困難的,但是可以發現這樣一件事,在兩個相鄰的點之間任意一個點建第一座橋的答案都將是一樣的,那麼這意味著我們第一座橋的有效位置只有 \(2n\)

個,那麼我們能否通過列舉這 \(2n\) 個位置來算出在第一座橋確定的情況下第二座橋最優的位置呢?可以發現這也是一個很困難的事情,因為每個點要在兩座橋之間走的距離取最小值,我們很難確定當前橋對所有人的影響。這時候感覺無路可走了,但注意到我們之前都是從橋來考慮對人的影響,我們不妨反過來,解決剛剛那個困難的問題,兩座橋中人會選擇哪座橋。手玩一些例子可以發現,貌似假如將每個人的 \(S_i, T_i\) 看作是一條線段,那麼他回選擇距離這條線段中點近的那座橋。證明分兩個端點和兩座橋的位置關係即可。

有了上面這個人選橋的最優方案,不難發現最終所有人(按中點從小到大排序)選橋的方案一定是左邊的人選一座橋,右邊的人選另一座橋。那麼左右兩邊橋的位置我們就可以使用 \(K = 1\)

的方法來求解了。但是中間的分界點還是難以確定,因此我們還需列舉中間的分界點。那麼我們怎樣快速計算答案呢?假設這 \(2n\) 個點的座標分別為 \(x_1 \cdots x_{2n}\),橋選在 \(d\),那麼答案的計算式應該是:

\[\sum\limits_{x_i > d} x_i - d + \sum\limits_{x_i < d} d - x_i \]

於是我們需要一個能查詢一組數的中位數,查詢比某個數小的數有多少個,以及這些數的權值為多少的資料結構,權值線段樹是一個好的選擇。

#include<bits/stdc++.h>
using namespace std;
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (l + r) / 2
#define rep(i, l, r) for(int i = l; i <= r; ++i)
typedef long long ll;
const int N = 200000 + 5;
const ll inf = 10000000000000000;
ll ans; 
char P[2], Q[2];
int n, m, K, S, T, d[N];
struct tree{
	int cnt[N << 2]; ll sum[N << 2];
	void update(int p, int l, int r, int x, int y, int k){
		if(l >= x && r <= y){ sum[p] += 1ll * d[l] * k, cnt[p] += k; return;}
		if(mid >= x) update(ls, l, mid, x, y, k);
		if(mid < y) update(rs, mid + 1, r, x, y, k);
		sum[p] = sum[ls] + sum[rs], cnt[p] = cnt[ls] + cnt[rs];
	}
	ll query(int p, int l, int r, int k){
		if(l == r) return 1ll * k * d[l];
		if(cnt[ls] >= k) return query(ls, l, mid, k); // 開始這裡複製 find 時忘記改了,調了一年
		else return sum[ls] + query(rs, mid + 1, r, k - cnt[ls]);
	}
	int find(int p, int l, int r, int k){
		if(l == r) return d[l];
		if(cnt[ls] >= k) return find(ls, l, mid, k);
		else return find(rs, mid + 1, r, k - cnt[ls]);
	}
}T1, T2;
struct node{
	int l, r;
}a[N], b[N];
int read(){
	char c; int x = 0, f = 1;
	c = getchar();
	while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}
namespace S1{
	int cnt = 0, c[N];
	void solve(){
		rep(i, 1, n) c[++cnt] = a[i].l, c[++cnt] = a[i].r;
		sort(c + 1, c + cnt + 1);
		rep(i, 1, cnt) ans += abs(c[i] - c[n]);
		printf("%lld", ans);
	}
}
namespace S2{
	int tot = 0; long long tmp = inf, res = 0, Sl = 0, Sr = 0;
	bool cmp(node a, node b){
		return a.l + a.r < b.l + b.r;
	}
	void solve(){
		sort(a + 1, a + n + 1, cmp);
		rep(i, 1, n) d[++tot] = a[i].l, d[++tot] = a[i].r;
		sort(d + 1, d + tot + 1);
		tot = unique(d + 1, d + tot + 1) - d - 1;
		rep(i, 1, n){
			b[i].l = lower_bound(d + 1, d + tot + 1, a[i].l) - d;
			b[i].r = lower_bound(d + 1, d + tot + 1, a[i].r) - d;
		}
		rep(i, 1, n) Sr += a[i].l, Sr += a[i].r;
		rep(i, 1, n) T2.update(1, 1, tot, b[i].l, b[i].l, 1), T2.update(1, 1, tot, b[i].r, b[i].r, 1);
		rep(i, 1, n){
			T1.update(1, 1, tot, b[i].l, b[i].l, 1), T1.update(1, 1, tot, b[i].r, b[i].r, 1);
			T2.update(1, 1, tot, b[i].l, b[i].l, -1), T2.update(1, 1, tot, b[i].r, b[i].r, -1);
			Sl += a[i].l, Sl += a[i].r, Sr -= a[i].l, Sr -= a[i].r;
			int P1 = T1.find(1, 1, tot, i), P2 = T2.find(1, 1, tot, n - i);
			ll S1 = T1.query(1, 1, tot, i), S2 = T2.query(1, 1, tot, n - i);
			res = 1ll * P1 * i - S1 + Sl - S1 - 1ll * P1 * i;
			res += 1ll * P2 * (n - i) - S2 + Sr - S2 - 1ll * P2 * (n - i);
			tmp = min(tmp, res);
		}
		printf("%lld", ans + (tmp == inf ? 0 : tmp));
	}
}
int main(){
	K = read(), m = read(); int num = 0;
	rep(i, 1, m){
		scanf("%s", P + 1), S = read(), scanf("%s", Q + 1), T = read();
		if(P[1] == Q[1]) ans += abs(S - T);
		else a[++num] = (node){S, T}, ++ans;
	}
	n = num;
	if(K == 1) S1 :: solve();
	else S2 :: solve();
	return 0;
}