[APIO2015]八邻旁之桥
注意到 \(K = 1, 2\) 于是我们可以从简单的 \(K = 1\) 开始入手。可以发现家和办公室在同一边的人不管建不建桥都是无所谓的,因此下面我们只需要考虑不在同一边的人,假设桥的位置在 \(d\) 那么答案可以简单的表示为:
这可以看作是 \(2n\) 个在数轴上的点到 \(d\) 的距离,这是一个很经典的问题,只需取这 \(2n\) 个点的中位数即可。
接下来再考虑 \(K = 2\) 的情况,如果我们想直接求出两座桥的位置是很困难的,但是可以发现这样一件事,在两个相邻的点之间任意一个点建第一座桥的答案都将是一样的,那么这意味着我们第一座桥的有效位置只有 \(2n\) 个,那么我们能否通过枚举这 \(2n\) 个位置来算出在第一座桥确定的情况下第二座桥最优的位置呢?可以发现这也是一个很困难的事情,因为每个点要在两座桥之间走的距离取最小值,我们很难确定当前桥对所有人的影响。这时候感觉无路可走了,但注意到我们之前都是从桥来考虑对人的影响,我们不妨反过来,解决刚刚那个困难的问题,两座桥中人会选择哪座桥。手玩一些例子可以发现,貌似假如将每个人的 \(S_i, T_i\) 看作是一条线段,那么他回选择距离这条线段中点近的那座桥。证明分两个端点和两座桥的位置关系即可。
有了上面这个人选桥的最优方案,不难发现最终所有人(按中点从小到大排序)选桥的方案一定是左边的人选一座桥,右边的人选另一座桥。那么左右两边桥的位置我们就可以使用 \(K = 1\) 的方法来求解了。但是中间的分界点还是难以确定,因此我们还需枚举中间的分界点。那么我们怎样快速计算答案呢?假设这 \(2n\) 个点的坐标分别为 \(x_1 \cdots x_{2n}\),桥选在 \(d\),那么答案的计算式应该是:
于是我们需要一个能查询一组数的中位数,查询比某个数小的数有多少个,以及这些数的权值为多少的数据结构,权值线段树是一个好的选择。
#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;
}