1. 程式人生 > 其它 >【Mysql學習】鎖

【Mysql學習】鎖

一年前我初見莫隊,一年後我才正式學習莫隊,現在kang來一年之際滄海桑田……

套用某葉老師的話來說,“緣分這個東西真的很奇妙”。

好吧。我開始初學莫隊啦。

概述

莫隊是一種離線演算法。它主要針對那些區間問題,而且需要維護的資訊比較複雜而無法使用線段樹等傳統資料結構維護的問題。它需要滿足兩個性質,一個是題目支援離線(什麼強制線上異或加密的莫隊搞不定),二是區間答案具有較強的“可加性”與可減性,也就是說,知道了[l,r]的答案,可以很快地求出[l,r+1]或[l+1,r] 的答案。

個人覺得,莫隊的本質是利用已求出的的答案來優化後續求解的過程。

一般來說,莫隊的複雜度為 \(O(N\sqrt{N})\)

或者再加一個log(如果帶修的話可能是\(N^{\frac{5}{3}}\)),一般來說可以應對\(10^5\)級別的資料量。實際過程中會發現寫出來還是挺簡單的,而且具有極強的拓展性。

接下來對四種莫隊進行概述。

普通莫隊

它能完成的就是多次詢問區間的一些資訊,比如區間內有多少個元素出現過奇數次。很顯然這個問題無法使用線段樹來維護(主席樹似乎也不行),直接用分塊的話複雜度過高,於是想到使用莫隊(事實上這是最基礎的那一類莫隊)。

演算法過程一般是這樣的:

首先對詢問進行分塊排序,保證複雜度(當然要記錄詢問出現的位置方便輸出):

inline bool cmp(node s1,node s2){
	if(s1.l/bl==s2.l/bl)return s1.r<s2.r;
	else return s1.l<s2.l;
}//bl一般取根號N

然後就是莫隊主體

inline void add(int wh){
	//加入操作並且更新答案
}
inline void del(int wh){
	//同上,處理刪除操作
}
signed main(){
	......
	int l=0,r=0;//初始化
	for(int i=1;i<=m;i++){//按順序處理每一個詢問
		while(r<q[i].r)add(a[++r]);
		while(l>q[i].l)add(a[--l]);
		while(r>q[i].r)del(a[r--]);
		while(l<q[i].l)del(a[l++]);
		//將莫隊區間移到當前的詢問區間。注意自加自減的寫法
		ans[q[i].id]=ans;
	}
	//輸出即可
	......
}

帶修莫隊

其實吧,帶修莫隊可以看成是一種三維序列(三維的能叫序列?大霧)的問題。唯一不同的是它應該先挪時間軸,而且只有當修改點在當前的莫隊區間裡(是莫隊區間而不是詢問區間!血與淚的教訓!)才對答案進行更新。另外就是為了平衡複雜度,塊的大小一般取\(N^{\frac{5}{3}}\),證明方法懶得寫了,反正這種結論性的東西會用就行。

用P1903的程式碼做一下示範:

//排序的cmp:
inline bool cmp(node s1,node s2){
	if(s1.l/bl!=s2.l/bl)return s1.l/bl<s2.l/bl;
	else if(s1.r/bl!=s2.r/bl)return s1.r/bl<s2.r/bl;
	else return s1.t<s2.t;
}
//其中bl=ceil(pow(N,0.66))+1;

//挪指標:
for(int i=1;i<=cnt;i++){
	//重點是挪時間軸中的判斷
	//當時由於人傻了調了一個半小時才調出來
	while(t<q[i].t){
		t++;a[ch[t].pl]=ch[t].data;
		if(ch[t].pl>=l&&ch[t].pl<=r){
			del(ch[t].bf);
			add(ch[t].data);
		}
	}
	while(t>q[i].t){
		if(ch[t].pl>=l&&ch[t].pl<=r){
			del(ch[t].data);
			add(ch[t].bf);
		}
		a[ch[t].pl]=ch[t].bf;t--;
	}
	while(r<q[i].r)add(a[++r]);
	while(l>q[i].l)add(a[--l]);
	while(r>q[i].r)del(a[r--]);
	while(l<q[i].l)del(a[l++]);
	an[q[i].id]=ans;
}

回滾莫隊

名字很高大上,然而我並不覺得它那個講法很通俗易懂。

回滾莫隊針對那種新增元素和刪除元素其中一個統計答案比較複雜或複雜度比較高的問題,也就是說它只支援把區間越擴越大而不支援把區間弄得稍微小一點。這就與我們的初衷產生了分歧,也就是說相交卻不包含的兩個區間無法互相得到答案,那麼複雜度也就無從談起了。

但是天無絕人之路。我們可以考慮對於這些詢問區間把它們砍成兩半,那麼我們可以強迫其中的一些半區間互相包含(簡單的一種做法是按照同樣的分界線去砍,這樣就會出現許多左端點對齊、右端點不同,當然可以讓它們遞增的半區間,這樣舊就使得這些詢問在某種意義上變得互相包含了),然後讓剩下的部分暴力求解即可。

實現上,可以考慮根號分塊,把所有跨塊區間按照左端點所在塊進行分類,然後分批進行處理。處理方法如上文,對於右端點排序從小到大依次處理,左半段暴力即可。至於那些塊內區間,很明顯它們的長度不會太大,暴力求解即可。

總的複雜度為\(O(N\sqrt{N})\),常數可能比普通莫隊大一些。

由於實現過程中要多次清空資料結構,可以考慮進行節點的時間標記優化。

比如這道題就要用回滾莫隊(我真的沒壓行!):歷史研究

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
//#define zczc
#define ll long long
using namespace std;
const int N=100010;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
int m,n,num,bl,a[N],b[N],s[N],t[N],nt;
ll an[N],ans,rans;
struct node{int l,r,pl;}q[N];
inline bool cmp(node s1,node s2){
	int r1=(s1.l-1)/bl,r2=(s2.l-1)/bl;
	if(r1==r2)return s1.r<s2.r;
	else return r1<r2;
}
inline void check(ll &s1,ll s2){if(s1<s2)s1=s2;return;}
inline void add(int wh){
	if(t[wh]!=nt)t[wh]=nt,s[wh]=0;s[wh]++;
	check(ans,(ll)b[wh]*s[wh]);
}
inline void del(int wh){s[wh]--;return;}
int ti[N],numm[N],nti;
inline void radd(int wh){
	if(ti[wh]!=nti){ti[wh]=nti;numm[wh]=0;}
	numm[wh]++;
	check(rans,(ll)b[wh]*numm[wh]);
}
void solve(int wh){
	nti++;rans=0;
	for(int i=q[wh].l;i<=q[wh].r;i++){radd(a[i]);}
	an[q[wh].pl]=rans;
}
signed main(){
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	read(m);read(n);
	for(int i=1;i<=m;i++){read(a[i]);b[i]=a[i];}
	sort(b+1,b+m+1);num=unique(b+1,b+m+1)-b-1;
	for(int i=1;i<=m;i++)a[i]=upper_bound(b+1,b+num+1,a[i])-b-1;
	for(int i=1;i<=n;i++){read(q[i].l);read(q[i].r);q[i].pl=i;}
	bl=(int)ceil(sqrt(m));sort(q+1,q+n+1,cmp);
	int now=1,lim=bl,l,r;ll rec;
	while(now<=n){
		ans=0;nt++;l=lim+1,r=lim;
		while(q[now].l<=lim&&q[now].l>lim-bl&&now<=n){
			if(q[now].r<=lim){
				solve(now);
				now++;
				continue;
			}
			while(r<q[now].r)add(a[++r]);
			rec=ans;l=lim+1;
			while(l>q[now].l)add(a[--l]);
			an[q[now].pl]=ans;
			while(l<=lim)del(a[l++]);
			ans=rec;now++;
		}
		lim+=bl;
	}
	for(int i=1;i<=n;i++)printf("%lld\n",an[i]);
	return 0;
}

樹上莫隊

莫隊完成了從無脊椎動物到有脊椎動物的蛻變!

它直立行走了!

——它可以處理樹上的問題啦!

當然它只能處理路徑上的問題,不過這已經很不錯了。同樣,它維護的是線段樹(也就是樹剖的底層資料結構)難以維護的資訊,比如顏色種類……實現上還是把樹上問題轉換成序列問題,這裡要藉助一個東西就是尤拉序(tmd怎麼到處都有尤拉!)

尤拉序就是升級版的dfn序,據說還可以用來\(O(1)\)線上求lca(雖然要\(O(N\log N)\)的預處理就顯得非常之拉跨),當然也可以用來處理樹上莫隊。具體做法懶得寫了,網上寫得一個比一個好。

比如這道題:蘋果樹,約等於是詢問樹上路徑顏色數(我不管,反正我真的沒壓行)。

#include<cstdio>
#include<cmath>
#include<algorithm>
//#define zczc
using namespace std;
const int N=100010;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
int m,n,a[N],s[N<<1],head[N],esum;
struct edge{int t,nxt;}e[N<<1];
inline void add(int fr,int to){esum++;e[esum].t=to;e[esum].nxt=head[fr];head[fr]=esum;}
int lg[N],nxt[N][20],d[N],cnt,p1[N],p2[N];
void dfs(int wh,int fa,int deep){
	s[++cnt]=wh;p1[wh]=cnt;d[wh]=deep;nxt[wh][0]=fa;
	for(int i=1;i<=lg[d[wh]];i++)nxt[wh][i]=nxt[nxt[wh][i-1]][i-1];
	for(int i=head[wh],th;i;i=e[i].nxt){th=e[i].t;if(th==fa)continue;dfs(th,wh,deep+1);}
	s[++cnt]=wh;p2[wh]=cnt;
}
inline void swap(int &s1,int &s2){int s3=s1;s1=s2;s2=s3;return;}
int lca(int s1,int s2){
	if(d[s1]<d[s2])swap(s1,s2);
	for(int i=lg[d[s1]];i>=0;i--)
		if(d[nxt[s1][i]]>=d[s2])s1=nxt[s1][i];
	if(s1==s2)return s1;
	for(int i=lg[d[s1]];i>=0;i--)
		if(nxt[s1][i]!=nxt[s2][i])s1=nxt[s1][i],s2=nxt[s2][i];
	return nxt[s1][0];
}
struct node{
	int l,r,a,b,pl,ll;
}q[N];
int bl;
inline bool cmp(node s1,node s2){
	if(s1.l/bl==s2.l/bl)return s1.r<s2.r;
	else return s1.l/bl<s2.l/bl;
}
bool in[N];
int num[N],an[N],ans;
inline void push(int wh,int val){
	num[wh]+=val;
	if(val==1&&num[wh]==1)ans++;
	if(val==-1&&num[wh]==0)ans--;
}
inline void change(int wh){
	if(wh==0)return;
	int co=a[wh];
	if(in[wh])push(co,-1);else push(co,1);
	in[wh]=!in[wh];
}
signed main(){
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	lg[0]=1;int s1,s2;
	for(int i=1;i<N;i++)lg[i]=lg[i>>1]+1;
	read(m);read(n);
	for(int i=1;i<=m;i++)read(a[i]);
	for(int i=1;i<=m;i++){read(s1);read(s2);if(s1==0||s2==0)continue;add(s1,s2);add(s2,s1);}
	dfs(1,0,1);
	for(int i=1;i<=n;i++){
		q[i].pl=i;read(s1);read(s2);read(q[i].a);read(q[i].b);int lc=lca(s1,s2);
		if(lc==s1||lc==s2){
			if(p1[s1]>p1[s2])swap(s1,s2);
			q[i].l=p1[s1];q[i].r=p1[s2];q[i].ll=0;
		}
		else{
			if(p1[s1]>p1[s2])swap(s1,s2);
			q[i].l=p2[s1];q[i].r=p1[s2];q[i].ll=lc;
		}
	}
	bl=ceil(sqrt(m*2))+1;sort(q+1,q+n+1,cmp);
	int l=0,r=0;
	for(int i=1;i<=n;i++){
		while(r<q[i].r)change(s[++r]);
		while(l>q[i].l)change(s[--l]);
		while(r>q[i].r)change(s[r--]);
		while(l<q[i].l)change(s[l++]);
		if(q[i].ll!=0)change(q[i].ll);
		if(q[i].a!=q[i].b&&num[q[i].a]!=0&&num[q[i].b]!=0)an[q[i].pl]=ans-1;
		else an[q[i].pl]=ans;
		if(q[i].ll!=0)change(q[i].ll);
	}
	for(int i=1;i<=n;i++)printf("%d\n",an[i]);
	return 0;
}
一如既往,萬事勝意