CF809D Hitchhiking in the Baltic States

Last_Breath發表於2021-05-25

前言

題目連結:洛谷

題目連結:CodeForces

驚了,splay 好寫還快但題解裡沒人用。

題意

給定 \(n\) 個區間 \([l_i,r_i]\) ,請你構造一個序列,每個元素 \(a_i\) 滿足 \(a_i\in[l_i,r_i]\) ,且該序列的最長嚴格上升子序列最長。

思路

\(n^2\) 的 DP 都想了半天 QAQ 。

首先在第一維列舉區間。

定義 \(dp[i][j]\) :列舉到第 \(i\) 個區間時,長度為 \(j\) 的嚴格上升子序列的結尾的最小值,若不存在,則為極大值。

顯然,初值為 \(dp[0][0]=0\) ,其餘為極大值。最後統計 \(dp[i](i\in n)\) 中不等於極大值的有多少。轉移方程如下:

\(dp[i][j]= \begin{cases} min(dp[i-1][j],l),& \text{dp[i-1][j-1]<l,}\\ min(dp[i-1][j],dp[i-1][j-1]+1),& \text{dp[i-1][j-1]>=l,dp[i-1][j-1]<r,}\\ dp[i-1][j],& \text{dp[i-1][j-1]>=r.} \end{cases}\)

可以陣列降維,將第一維刪去,然後倒著更新。暴力 DP 虛擬碼:

for(int i = 1; i <= n; i++) {
	for(int j = n; j >= 1; j--) {
		if(dp[j - 1] < l[i]) dp[j] = Min(dp[j], l[i]);
		if(l[i] <= dp[j - 1] && dp[j - 1] < r[i]) dp[j] = Min(dp[j], dp[j - 1] + 1);
	}
}

在考慮用平衡樹優化。

由於是從後往前更新,所以先考慮第二類 dp 的影響。第二類 dp 可以分解為三部:

  1. 先刪除第一個大於等於 \(r\) 的點。
  2. 區間 \([l,r)\) 的所有數平移為 \([l+1,r+1)\)
  3. 最後插入實行第二部前,第一個大於等於 \(l\) 的數。

值得注意的是,若區間 \([l,r)\) 沒有任何數,則忽略第二步操作。

在執行第一類 dp ,可以發現,它只更新一個數,即第一個大於等於 \(l\) 的數。具體分為兩步:

  1. 先刪除第一個大於等於 \(l\) 的數。
  2. 插入 \(l\)

可以將其合併為 \(3\) 個操作,即第二類 dp 的 \(1\)\(2\) 操作,與第一類 dp 的 \(2\) 操作。

由於涉及到區間操作,需要打懶標記。

Code

#include <cstdio>
namespace Quick_Function {
	template <typename Temp> void Read(Temp &x) {
		x = 0; char ch = getchar(); bool op = 0;
		while(ch < '0' || ch > '9') { if(ch == '-') op = 1; ch = getchar(); }
		while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
		if(op) x = -x;
	}
	template <typename T, typename... Args> void Read(T &t, Args &... args) { Read(t); Read(args...); }
	template <typename Temp> Temp Max(Temp x, Temp y) { return x > y ? x : y; }
	template <typename Temp> Temp Min(Temp x, Temp y) { return x < y ? x : y; }
	template <typename Temp> Temp Abs(Temp x) { return x < 0 ? (-x) : x; }
	template <typename Temp> void Swap(Temp &x, Temp &y) { x ^= y ^= x ^= y; }
}
using namespace Quick_Function;
#define INF 0x3f3f3f3f
const int MAXN = 6e5 + 5;
int n, ans;
struct Splay_Node {
	int son[2], fa, val, tag;
	#define ls t[pos].son[0]
	#define rs t[pos].son[1]
};
struct Splay_Tree {
	Splay_Node t[MAXN];
	int root, tot, Top, stk[MAXN];
	int Ident(int pos) { return t[t[pos].fa].son[1] == pos ? 1 : 0; }
	void Connect(int pos, int fa, int flag) { t[fa].son[flag] = pos, t[pos].fa = fa; }
	void Push_Down(int pos) {
		if(!t[pos].tag) return;
		if(ls) { t[ls].val += t[pos].tag, t[ls].tag += t[pos].tag; }
		if(rs) { t[rs].val += t[pos].tag, t[rs].tag += t[pos].tag; }
		t[pos].tag = 0;
	}
	int New(int val, int fa) { t[++tot].fa = fa, t[tot].val = val; return tot; }
	void Build() { root = New(-INF, 0); t[root].son[1] = New(INF, root); }
	void Rotate(int pos) {
		int fa = t[pos].fa, grand = t[fa].fa;
		int flag1 = Ident(pos), flag2 = Ident(fa);
		Connect(pos, grand, flag2);
		Connect(t[pos].son[flag1 ^ 1], fa, flag1);
		Connect(fa, pos, flag1 ^ 1);
	}
	void Splay(int pos, int to) {
		int tmp = pos; Top = 0;
		stk[++Top] = tmp;
		while(tmp) stk[++Top] = tmp = t[tmp].fa;
		while(Top) Push_Down(stk[Top--]);//以上為釋放懶標記
		for(int fa = t[pos].fa; t[pos].fa != to; Rotate(pos), fa = t[pos].fa)
			if(t[fa].fa != to) Ident(pos) == Ident(fa) ? Rotate(fa) : Rotate(pos);
		if(!to) root = pos;
	}
	void Insert(int &pos, int val, int fa) {
		if(!pos) { ++ans; pos = New(val, fa); Splay(pos, 0); return; }
		Push_Down(pos);//幾乎每個操作都有釋放懶標,別忘了
		if(val < t[pos].val) Insert(ls, val, pos);
		else Insert(rs, val, pos);
	}
	void Erase(int pos) {
		Splay(pos, 0);
		int l = ls, r = rs;
		while(t[l].son[1]) l = t[l].son[1];
		while(t[r].son[0]) r = t[r].son[0];
		Splay(l, 0); Splay(r, l);
		t[r].son[0] = 0; --ans;
	}
	int Get_Pre(int val) {//查詢第一個小於val的點的編號
		int pos, res, newroot;
		pos = newroot = root;
		while(pos) {
			Push_Down(pos);
			if(t[pos].val < val) { res = pos; pos = rs; }
			else pos = ls;
		}
		Splay(newroot, 0);
		return res;
	}
	int Get_Nxt(int val) {//查詢第一個大於val的點的編號
		int pos, res, newroot;
		pos = newroot = root;
		while(pos) {
			Push_Down(pos);
			if(t[pos].val > val) { res = pos; pos = ls; }
			else pos = rs;
		}
		Splay(newroot, 0);
		return res;
	}
	void Move(int l, int r) {//平移區間[l,r)
		int u = Get_Nxt(l - 1), v = Get_Pre(r);
		if(t[u].val > t[v].val) return;//沒有數字就跳過
		if(u == v) t[u].val++;
		else if(t[u].val < t[v].val) {
			Splay(u, 0); Splay(v, u);
			int rson = t[v].son[0];
			++t[u].val; ++t[v].val, t[rson].val++;
			if(rson) ++t[rson].tag;
		}
	}
};
Splay_Tree tree;
int main() {
	Read(n); tree.Build();
	tree.Insert(tree.root, 0, 0); ans = 0;
	for(int i = 1, l, r; i <= n; i++) {
		Read(l, r);
		int pos = tree.Get_Nxt(r - 1);
		if(pos && pos != 1 && pos != 2) tree.Erase(pos);
		tree.Move(l, r);
		tree.Insert(tree.root, l, 0);
	}
	printf("%d", ans);
	return 0;
}