[ZJOI2012][BZOJ2658]小藍的好友(Treap維護笛卡爾樹)
題面
https://darkbzoj.tk/problem/2658
題解
前置知識
-
笛卡爾樹 https://blog.csdn.net/qq_36056315/article/details/79845193
-
FHQ Treap https://blog.csdn.net/CABI_ZGX/article/details/79963427
-
或普通Treap http://www.360doc.com/content/19/0120/11/5315_810146183.shtml
看見“至少包含一個”這種字眼,多少感覺正向做會比取補要來得麻煩一些,於是取補,從矩形總數\(\frac{R(R+1)}{2}\times\frac{C(C+1)}{2}\)
在統計不包含任何點的矩形的個數時,先從上到下列舉矩形的下邊所在直線。(不妨設x軸正方向向右,y軸正方向向下)設當前直線為\(y=cury\),對橫座標x=1~R,定義h[x]為所有橫座標為x,縱座標\(\leq cury\)的點中,縱座標最大者的縱座標。那麼所有下邊在\(y=cury\)上的矩形的總數就是
\[{\sum\limits_{i{\leq}j}}\min\limits_{k=i}^{j}h[k]-cury \]\[=cury \times \frac{R(R+1)}{2} - \sum\limits_{i{\leq}j}\min\limits_{k=i}^{j}h[k] \]而第二項可以轉化
\[{\sum\limits_{i{\leq}j}}\min\limits_{k=i}^{j}h[k] \]對h陣列建出笛卡爾樹後,分別對樹上每一個節點u考慮貢獻:h[u]對點對(i,j)有貢獻,當且僅當\(i{\leq}u{\leq}j\)且i,j都在u的子樹內。所以u此時的總貢獻是\(h[u]*(sz[c[u][0]]+1)(sz[c[u][1]]+1)\)。
現在就變成了一道純資料結構題。要求在笛卡爾樹上,維護一個序列h,每次可以單點修改,或者查詢總體的\(h[u]*(sz[c[u][0]]+1)(sz[c[u][1]]+1)\)的和。
但是單靠笛卡爾樹無法修改啊?並不是,其實笛卡爾樹本身“相容”插入刪除等操作,因為它的結構和Treap一模一樣,Treap中的權值對應笛卡爾樹中的key(本題中key是行編號),優先順序對應val(本題中是h)。Treap中的insert,remove函式(或者FHQ Treap中的split,merge)笛卡爾樹同樣能用。
怎麼修改呢?相當於把某一個點拿出來並從樹中刪掉,修改它的值,再重新加進樹裡去。如果用FHQ Treap的話,就相當於把某一個點和它前後全部拆開,修改它的值,再和它前後全部merge起來。
總時間複雜度\(O(n \log n + C \log R)\)。
程式碼
#include<bits/stdc++.h>
using namespace std;
#define rg register
#define In inline
#define ll long long
const int L = 40000;
const int N = 100000;
typedef pair<ll,ll>pll;
In ll read(){
ll s = 0,ww = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')ww = -1;ch = getchar();}
while('0' <= ch && ch <= '9'){s = 10 * s + ch - '0';ch = getchar();}
return s * ww;
}
int n;
ll R,C;
struct CartTree{
int cnt,rt;
int c[N+5][2],val[N+5],pri[N+5]; //val即第幾列,pri即h
ll sz[N+5],sum[N+5];
int create(int x){
cnt++;
val[cnt] = x;
pri[cnt] = 0; //初始h都為0
sz[cnt] = 1;
return cnt;
}
void pushup(int u){
int lc = c[u][0],rc = c[u][1];
sz[u] = sz[lc] + sz[rc] + 1;
sum[u] = sum[lc] + sum[rc] + pri[u] * (sz[lc] + 1) * (sz[rc] + 1);
}
int build(int l,int r){
if(l == r)return create(l);
int m = (l + r) >> 1;
int u = create(m);
if(l <= m - 1)c[u][0] = build(l,m - 1);
if(m + 1 <= r)c[u][1] = build(m + 1,r);
pushup(u);
return u;
}
int merge(int u,int v){
if(!u || !v)return u + v;
if(pri[u] > pri[v]){
c[u][1] = merge(c[u][1],v);
pushup(u);
return u;
}
else{
c[v][0] = merge(u,c[v][0]);
pushup(v);
return v;
}
}
void split(int u,int x,int &v,int &w){
if(!u)v = w = 0;
else{
if(val[u] <= x){
v = u;
split(c[v][1],x,c[v][1],w);
}
else{
w = u;
split(c[w][0],x,v,c[w][0]);
}
pushup(u);
}
}
void ud(int i,int x){ //將h[i]修改為x
int u,v,w;
split(rt,i,u,w);
split(u,i - 1,u,v);
pri[v] = x;
pushup(v);
rt = merge(merge(u,v),w);
}
ll query(){
return sum[rt];
}
}T;
pll p[N+5];
int main(){
R = read();C = read();n = read();
for(rg int i = 1;i <= n;i++){
p[i].second = read(),p[i].first = read();
}
T.rt = T.build(1,R);
sort(p + 1,p + n + 1);
int cur = 0;
ll ans = 0;
for(rg ll cury = 1;cury <= C;cury++){
while(cur < n && p[cur+1].first == cury){
cur++;
T.ud(p[cur].second,cury);
}
ans += cury * R * (R + 1) / 2;
ans -= T.query();
}
ans = R * (R + 1) / 2 * C * (C + 1) / 2 - ans;
cout << ans << endl;
return 0;
}