1. 程式人生 > 實用技巧 >[ZJOI2012][BZOJ2658]小藍的好友(Treap維護笛卡爾樹)

[ZJOI2012][BZOJ2658]小藍的好友(Treap維護笛卡爾樹)

題面

https://darkbzoj.tk/problem/2658

題解

前置知識

看見“至少包含一個”這種字眼,多少感覺正向做會比取補要來得麻煩一些,於是取補,從矩形總數\(\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;
}