1. 程式人生 > 實用技巧 >Django Models Choices 詳解

Django Models Choices 詳解

可持久化線段樹(主席樹)

單點修改

​ 1.單點修改時,我們考慮將包含該點\(k\)的線段樹節點新建出一條鏈。(就像這樣) 每次修改將創造出\(logn\)個新節點。

​ 2.修改完的線段樹不再是一顆完全二叉樹,我們不能直接用層次編號,而是直接改為記錄左右子節點的編號。大概的意思就是:不能用\(o << 1\)的方式去找o點的左兒子,而是要在結構體裡新加一個東西,用\(t[o].lc\)去找他的左兒子。

struct Tree {
    int lc, rc; //左右子樹編號
    int dat; //區間最大值
} t[N << 2];
int tot, root[N]; //可持久化線段樹的總點數和每個根
int n, a[N];

void up(int p) {
	t[p].dat = max(t[t[p].lc].dat, t[t[p].rc].dat);
} 

int build(int l, int r) {
	int p = tot++;
    if(l == r) { t[p].dat = a[l]; return p; }
    int mid = (l + r) >> 1;
    t[p].lc = build(l, mid); t[p].rc = build(mid + 1, r);
    up(p); return p;
}

root[0] = build(1, n); //呼叫入口

int insert(int now, int l, int r, int x, int val) {
    int p = tot++;
    t[p] = t[now];
    if(l == r) { t[p].dat = val; return ; }
    int mid = (l + r) >> 1;
    if(x <= mid) t[p].lc = insert(t[now].lc, l, mid, x, val);
    if(x > mid) t[p].rc = insert(t[now].rc, mid + 1, r, x, val);
    up(p); return p;
}

root[i] = insert(root[i - 1], 1, n, x, val); //呼叫入口

cout << ask(root[v], 1, n, x);

​ 然後這個題就可以做了:

P3919 【模板】可持久化線段樹 1(可持久化陣列)

區間第k小值

​ 思想個上面那個差不多,只是用主席樹的方法做。

P3834 【模板】可持久化線段樹 2(主席樹)(和poj2761一樣,但是上面那個程式碼在poj過了,在luogu就過不了,不知道為啥,就很ex)

我是看這個大佬的部落格看懂的

​ 通過模擬資料來發現一些規律:

7
1 5 2 6 3 7 4
2 5 3

(第一棵樹)


(第二棵樹)

​ 像這樣一直插完。。。

(第七棵樹)

​ 現在我們要詢問區間[2, 5]第3大值,我們把第一棵樹第五棵樹拿出來。

​ 我們可以發現,對應節點的數字相減就可以得到1區間[2, 5]的數。(有點類似於字首和)

​ 我們設\(u\)為編號小的樹, \(v\)為編號大的樹,設\(x = t[t[v.ls]].sum - t[t[u.ls]].sum\)

。若\(k <= x\),則說明要找的數在左子樹內;若\(k > x\),則說明要找的數在右子樹內,於是我們去右子樹找第\(k - x\)小的數。(和上面那個是不是很像)

​ 這個主席樹上是值域。

#include <iostream>
#include <cstdio>
#include <algorithm>
#define mid ((l + r) >> 1)

using namespace std;

inline int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
	return s * f;
}

const int N = 2e5 + 5;
int n, m, tot;
int a[N], b[N], root[N * 20];
struct tree { int lc, rc, sum; } t[N * 20];

int build(int l, int r) {
	int p = ++tot;
	if(l == r) { return p; }
	t[p].lc = build(l, mid); t[p].rc = build(mid + 1, r);
	return p; //一定要記得寫,不然會RE
}

int change(int now, int l, int r, int k) {
	int p = ++tot;
	t[p].lc = t[now].lc; t[p].rc = t[now].rc; t[p].sum = t[now].sum + 1; 
	if(l == r) { return p; }
	if(k <= mid) t[p].lc = change(t[now].lc, l, mid, k);
	if(k > mid) t[p].rc = change(t[now].rc, mid + 1, r, k);
	return p; //一定記得寫
}

int query(int u, int v, int l, int r, int k) {
	if(l == r) return l;
	int x = t[t[v].lc].sum - t[t[u].lc].sum;
	if(k <= x) return query(t[u].lc, t[v].lc, l, mid, k);
	if(k > x) return query(t[u].rc, t[v].rc, mid + 1, r, k - x);
}

int main() {
	
	n = read(); m = read();
	for(int i = 1;i <= n; i++) b[i] = a[i] = read();
	sort(b + 1, b + n + 1); 
	int cnt = unique(b + 1, b + n + 1) - b - 1;
	root[0] = build(1, cnt); //這是那個空樹,注意那個cnt,離散化之後序列長度就變為cnt了(把重複的數去掉了)
	for(int i = 1;i <= n; i++) {
		a[i] = lower_bound(b + 1, b + cnt + 1, a[i]) - b;
		root[i] = change(root[i - 1], 1, cnt, a[i]);
	}
	for(int i = 1;i <= m; i++) {
		int x = read(), y = read(), k = read();
		printf("%d\n", b[query(root[x - 1], root[y], 1, cnt, k)]);
	}
	
	return 0;
}

水題

P3567 [POI2014]KUR-Couriers

題目大意:給一個數列,每次詢問一個區間內有沒有一個數出現次數超過一半。沒有的話輸出0。

主席樹水題。。。

​ 我們現在要找一個區間[\(l\), \(r\)]的一個數出現次數大於\((r - l + 1)/2\)。令\(x = (r - l + 1)/2\), 我們像上面區間第\(k\)小值一樣維護一個\(sum\)。如果一個節點的左兒子的\(sum * 2\)小於等於\(x\),那麼這裡面肯定沒有符合要求的數。如果大於,這個數則在左子樹內;右子樹同理。

#include <iostream>
#include <cstdio>
#include <cctype>
#define mid ((l + r) >> 1)

using namespace std;

inline long long read() {
	long long s = 0, f = 1; char ch;
	while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
	for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
	return s * f;
} 

const int N = 7e5 + 5;
int n, m, tot;
int root[N];
struct tree { int lc, rc, sum; } t[N * 20];

int build(int l, int r) {
	int p = ++tot;
	if(l == r) { return p; }
	t[p].lc = build(l, mid); t[p].rc = build(mid + 1, r);
	return p;
}

int insert(int res, int l, int r, int x) {
	int p = ++tot; 
	t[p].lc = t[res].lc; t[p].rc = t[res].rc; t[p].sum = t[res].sum + 1;
	if(l == r) { return p; }
	if(x <= mid) t[p].lc = insert(t[res].lc, l, mid, x);
	if(x > mid) t[p].rc = insert(t[res].rc, mid + 1, r, x);
	return p;
}

int query(int L, int R, int l, int r, int x) {
	if(l == r) { return l; }
	int num1 = t[t[R].lc].sum - t[t[L].lc].sum;
	int num2 = t[t[R].rc].sum - t[t[L].rc].sum;
	if(num1 * 2 > x) return query(t[L].lc, t[R].lc, l, mid, x);
	else if(num2 * 2 > x) return query(t[L].rc, t[R].rc, mid + 1, r, x);
	else return 0;
}

int main() {
	
	n = read(); m = read();
	root[0] = build(1, n);
	for(int i = 1, x;i <= n; i++) {
		x = read();	root[i] = insert(root[i - 1], 1, n, x);
	}
	for(int i = 1, l, r;i <= m; i++) {
		l = read(); r = read();
		printf("%d\n", query(root[l - 1], root[r], 1, n, r - l + 1));
	}

	return 0;
}