P2611 [ZJOI2012] 小蓝的好友 题解
题意:
具体的说,你需要计算有多少个四元组 \((LB,DB,RB,UB)\) 满足 \(1\le LB\le RB\le R,1\le DB\le UB\le C\) ,且存在一个 \(i\) 使得 \(LB\le xi\le RB,DB\le yi\le UB\) 均成立。
\(1\le R,C\le 4\times 10^4\),\(1\le N\le 10^5\),题目保证资源点的位置两两不同,且位置为随机生成。
分析:
考虑枚举下边界,从上往下枚举。那么对于每个下边界对答案的贡献为:
\[\sum_{L=1}^{m}\sum_{R=L}^{m} (\max_{i=L}^{R} h_{i}) \times \frac{(R-L+1)(R-L+2)}{2}
\]
显然可以枚举中间的 \(\max\),然后可以建立笛卡尔树(大根堆),它的 \(pri\) 为 \(h_{i}\),\(val\) 为 \(i\)。这样做后,答案贡献变为
\[\sum_{i=1}^{m} (siz_{ls_{i}}+1) \times (siz_{rs_{i}}+1) \times pri
\]
可以发现,这样做能使得一个区间的贡献只被一个节点算到。
可以使用 FHQ-Treap,由于点是随机的时间复杂度为 \(O(R \log n)\)。
代码:
#include<bits/stdc++.h>
#define int long long
#define N 200005
using namespace std;
int n, m, k, root, ans;
vector<int>p[N];
int siz[N], ls[N], rs[N], val[N], pri[N], sum[N], tot;
int newnode(int Val, int Pri) {
tot++;
val[tot] = Val;
pri[tot] = Pri;
siz[tot] = 1;
return tot;
}
void pushup(int u) {
siz[u] = siz[ls[u]] + siz[rs[u]] + 1;
sum[u] = sum[ls[u]] + sum[rs[u]] + (siz[ls[u]] + 1) * (siz[rs[u]] + 1) * pri[u];
}
void split(int u, int &x, int &y, int k) {
if(u == 0) {
x = y = 0;
return;
}
if(val[u] <= k) {
x = u;
split(rs[u], rs[u], y, k);
}
else {
y = u;
split(ls[u], x, ls[u], k);
}
pushup(u);
}
int merge(int x, int y) {
if(x == 0 || y == 0) return x + y;
if(pri[x] > pri[y]) {
rs[x] = merge(rs[x], y);
pushup(x);
return x;
}
else {
ls[y] = merge(x, ls[y]);
pushup(y);
return y;
}
}
void Insert(int Val, int Pri) {
int x, y;
split(root, x, y, Val);
root = merge(merge(x, newnode(Val, Pri)), y);
}
void Erase(int Val) {
int x, y, z;
split(root, x, y, Val - 1);
split(y, y, z, Val);
root = merge(x, z);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
srand(time(0));
cin >> n >> m >> k;
for(int i = 1, x, y; i <= k; i++) {
cin >> x >> y;
p[x].push_back(y);
}
for(int i = 1; i <= m; i++) Insert(i, rand());
for(int i = 1; i <= m; i++) pri[i] = 0, sum[i] = 0;
for(int i = 1; i <= n; i++) {
for(auto y : p[i]) {
Erase(y);
Insert(y, i);
}
ans += sum[root];
}
cout << ans;
return 0;
}