IOI2007 sails 船帆 - 题解
IOI2007 sails 船帆
题意:
让我们来建造一艘新的海盗船。船上有 N个旗杆,每根旗杆被分成单位长度的小节。旗杆的长度等于它被分成的小节的数目。每根旗杆上会挂一些帆,每张帆正好占据旗杆上的一个小节。在一根旗杆上的帆可以任意排布在不同的小节中,但是每个小节上至多能挂一张帆。
在风中,帆的不同排布方式会产生不同的推动力。靠近船头的帆比它后面的相同高度上的帆获得的推动力少,换句话说,靠近船头的帆的推动力由于受它后面相同高度的帆的影响而打了折扣。对于任意一张帆,它的推动力折扣等于在它后面并且和它在同一高度的帆的数目。
所有帆的任意一种位置组合的推动力折扣总和等于在该位置下所有帆的推动力折扣的总和。
做法:
首先根据答案的统计方式,我们发现每一层的贡献只与这一层帆的个数有关,所以旗杆的顺序对答案没有影响。
根据贪心,我们希望每一高度下帆的个数尽可能少,或者说让每一个高度下帆的个数尽可能平均。
由此,将旗杆高度从小到大排序,这个问题转换成了一个这样的问题:每次在一列数中添加若干个 0(相当于当前旗杆高度与前一次增加了多少),并将这些数中前 K小的数加 1(相当于贪心地选择船帆挂的位置),最后询问这列数中每个数 $ x_i $ , $ Ans = x_i \times (x_i - 1) / 2 $ 。这显然可以用平衡树维护(我用的是 Treap)。
然后我们发现每次修改后不能直接合并 Treap(这样会破坏二叉搜索树的性质)。如对于数列 $ ( 1, 1, 2, 2, 3, 3 ) $ ,将前三个数加 1,就变成了 $ ( 2, 2, 3 ) $ 和 $ ( 2, 3, 3 ) $ ,不能合并。考虑修改后修改的一段数中最大值为 x,那么没修改的数中小于 x的数相对位置向左移动,修改的数中等于 x的数相对位置向有移动,即 $ ( 2, 2, 3 ) $ 和 $ ( 2, 3, 3 ) $ 又拆分成 $ ( 2, 2 ), ( 3 ), ( 2 ), ( 3, 3 ) $,然后交换靠中间的两组再从左向右合并即可。
#include <bits/stdc++.h>
#define mp make_pair
#define fst first
#define snd second
#define rep(i, a, b) for(int i = (a), i##ed = (b); i <= i##ed; i++)
#define per(i, a, b) for(int i = (a), i##ed = (b); i >= i##ed; i--)
typedef long long ll;
typedef std::pair<int,int> pii;
const int N = 100010;
int n, pos = 0;
pii a[N];
int v[N], mx[N], lc[N], rc[N], p[N], lz[N], tot = 0, rt = 0, sz[N];
int sed = 19491001;
ll ans = 0;
inline int Rand() {
sed = (int)((ll)sed * 19260817ll % 1000000007ll); return sed;
}
inline int Max(int x, int y) { return x > y ? x : y; }
inline int Node(int w) {
v[++tot] = w, mx[tot] = w, sz[tot] = 1, p[tot] = Rand(); return tot;
}
void pushup(int u) {
mx[u] = Max(v[u], Max(mx[lc[u]], mx[rc[u]]));
sz[u] = sz[lc[u]] + sz[rc[u]] + 1;
}
void pushdown(int u) {
if(!lz[u]) return ;
if(lc[u]) lz[lc[u]] += lz[u], mx[lc[u]] += lz[u], v[lc[u]] += lz[u];
if(rc[u]) lz[rc[u]] += lz[u], mx[rc[u]] += lz[u], v[rc[u]] += lz[u];
lz[u] = 0;
}
void merge(int &u, int s, int b) {
if(!s || !b) { u = s + b; return ; }
pushdown(s), pushdown(b);
if(p[s] < p[b]) u = s, merge(rc[u], rc[s], b);
else u = b, merge(lc[u], s, lc[b]);
pushup(u);
}
void split(int u, int &l, int &r, int ss) {
if(!u) { l = r = 0; return ; }
pushdown(u);
if(sz[lc[u]] < ss) l = u, split(rc[u], rc[l], r, ss - sz[lc[u]] - 1);
else r = u, split(lc[u], l, lc[r], ss);
pushup(u);
}
void splitv(int u, int &l, int &r, int w) {
if(!u) { l = r = 0; return ; }
pushdown(u);
if(v[u] <= w) l = u, splitv(rc[u], rc[l], r, w);
else r = u, splitv(lc[u], l, lc[r], w);
pushup(u);
}
void insert(int w) { int z = Node(w); merge(rt, z, rt); }
void mdy(int ss) {
int x = 0, y = 0, z = 0, w = 0;
split(rt, y, w, ss);
++v[y], ++mx[y], ++lz[y];
splitv(w, z, w, mx[y]), splitv(y, x, y, mx[y] - 1);
merge(x, x, z), merge(y, y, w), merge(rt, x, y);
}
void dfs(int u) {
pushdown(u); if(lc[u]) dfs(lc[u]); if(rc[u]) dfs(rc[u]);
if(v[u]) ans = ans + (ll)v[u] * (v[u] - 1) / 2ll;
}
int main() {
scanf("%d", &n); rep(i, 1, n) scanf("%d%d", &a[i].fst, &a[i].snd);
std::sort(a + 1, a + n + 1);
rep(i, 1, n) {
for(; pos < a[i].fst; insert(0), ++pos);
mdy(a[i].snd);
}
dfs(rt), printf("%lld\n", ans);
return 0;
}