知識點的模板整理與複習

模板整理——by hkhh

非圖論:

一、二分有關

1):二分查找

手敲模板:

int find(int x){//假設從小到大排序,找第一個大於等於x的數
    int l = 1 , r  = n;
	while (l+1<r){
	    int mid = l+r >> 1;
		if (a[mid] == x) return mid;//如果找到,返回位置
		if (a[mid] > x) r = mid;
		else l = mid;
	}
	if (a[l]>=x) return l;
	else if (a[r]>=x) return r;
	else return -1;//找不到
}

stl自定義:

1.lower_bound(a+1,a+n+1,num)-a //尋找a數組裏面第一個>=x的數的位置
2.upper_bound(a+1,a+n+1,num)-a //尋找a數組第一個>x的數的位置
vector 中: lower_bound(a.begin(),a.end(),num)-a.begin() // 在vector< int > a中尋找第一個>=num的數的位置
2):二分答案:
int l = 1 , r = n;
while (l+1 < r){
	int mid = l+r>>1;
	if (check(mid)) l = mid;
	else r = mid;//這裏的操作根據不同題意而定
}
if (check(l)) return l;else return r;

二、樹狀數組

1)定義(好像也沒啥定義可以說):

樹狀數組主要是基於二進制思想,通過lowbit(刪數二進制位的最後一位0)來實現前綴和的累加操作,其實是將一段連續的區間分成幾段由2的整次冪所組成的區間的和,這樣便使線性複雜度變爲log複雜度

2)主要的幾個函數

lobiwt函數(核心)

#define lowbit(x) x&-x

求和函數

int Ask(int x){
	int ans = 0;
	for (int i = x;i;i-=lowbit(i)) 
	  ans+=t[i];
	return ans;
}

修改函數

void Change(int x,int v){
	for (int i = x;i <= maxx;i+=lowbit(i)) t[i]+=v;
}

代碼實現:

#define lowbit(x) x&-xint Ask(int x){
	int ans = 0;
	for (int i = x;i;i-=lowbit(i)) 
	  ans+=t[i];
	return ans;
}
void Change(int x,int v){
	for (int i = x;i <= maxx;i+=lowbit(i)) t[i]+=v;
}
3)稍加改變

區間修改可以用查分實現

Change(i,v);
Change(j+1,-v);

然後這個差分數組也可以用樹狀數組維護

二維樹狀數組
搞一搞就好


三、並查集

1)用途:主要爲了維護圖的連通性以及集合之間的關係
2)優化:路徑壓縮可以極大的優化時間
3)幾個函數:

查找祖先

int getfa(int k){
	return fa[k] == k?k:fa[k] = getfa(fa[k]);
}

合併以及判斷連通性

int x = getfa(x) , y = getfa(y);
if (x != y) fa[x]=y;//如果不連通,就並在一起

四、數學有關

1)六個函數:
int add(int a,int b){return a+b>=P?a+b-P:a+b;}
void Add(int &a,int b){a = add(a,b);}
//加
int sub(int a,int b){return a-b<0?a-b+P:a-b;}
void Sub(int &a,int b){a = sub(a,b);}
//減
int mul(int a,int b){return 1ll*a*b%P;}
void Mul(int &a,int b){a = mul(a,b);}
//乘

這樣似乎就可以防止因爲一些精度、模數問題而爆炸了

2)快速冪
int quickm(int x,int y){//x^y
	int ans = 1;
	for ( ; y ;y>>=1 , Mul(x,x))
	  if (y&1) Mul(ans,x);
    return ans;
}
3)快速乘(其實是龜速乘(複雜度爲log),主要是防止爆long long)
int quickc(int x,int y){
	int ans = 0;
	for ( ; y ;y>>=1 , Add(x,x))
	  if (y&1) Add(ans,x);
    return ans;
}

4) 最大公因數

int gcd(int x,int y){
	return x%y == 0?y:gcd(y,x%y);
}

五、分塊相關

這裏給出基本的區間修改,區間查詢的板子

0)變量解釋:

a[i]:a[i]:不用解釋
L[i]:iL[i]:第i個塊的左端點
R[i]:iR[i]:第i個塊的右端點
pos[i]:ipos[i]:第i個點所屬於的塊的編號
sum[i]:isum[i]:第i塊的累加和
add[i]:iadd[i]:第i塊的增量

1)基本分塊:

分塊(預處理)

int t = sqrt(n);
for (int i=1;i<=t;i++){
	L[i] = (i-1)*t+1;
	R[i] = i*t;
}//將區間分成sqrt(n)段
if (R[t] < n) L[++t] = R[t-1]+1,R[t] = n;//可能會有剩下的
for (int i=1;i<=t;i++)
  for (int j=L[i];j<=R[i];j++)
    pos[j] = i , sum[i]+=a[j];//記錄每一個點屬於的塊的編號,求出初始的時候每一個塊的和

修改操作

void Change(int l,int r,int v){
	int p = pos[l] , q = pos[r];
	if (p == q){
		for (int i=l;i<=r;i++) a[i]+=v;
		sum[p]+=(r-l+1)*v;
	}//如果不在一個塊內,直接暴力求
	else {
		for (int i=p+1;i<=q-1;i++) add[i]+=v;//中間包含的塊可以直接求
		for (int i=l;i<=R[p];i++) a[i]+=v;
		sum[p]+=(R[p]-l+1)*v;
		for (int i=L[q];i<=r;i++) a[i]+=v;
		sum[q]+=(r-L[q]+1)*v;//兩邊暴力求
	}
}

求和操作

int Ask(int l,int r){
	int ans = 0;
	int p = pos[l] , q = pos[r];
	if (p == q){
		for (int i=l;i<=r;i++) ans+=a[i];
		ans+=add[p]*(r-l+1);
	}
	else {
		for (int i=p+1;i<=q-1;i++) ans+=sum[i],ans+=add[i]*(R[i]-L[i]+1);
		for (int i=l;i<=R[p];i++) ans+=a[i];ans+=add[p]*(R[p]-l+1);
		for (int i=L[q];i<=r;i++) ans+=a[i];ans+=add[q]*(r-L[q]+1);
	}
	return ans;
}//同樣操作

2)分塊改進:莫隊

原因:對於有限、簡單的區間修改、區間查詢問題,分塊是很優秀的。但是當區間查詢的量到達一定程度、需要維護的量變得不好處理的時候,分塊就顯得乏力了。在這種情況下,我們引入莫隊

基本思想:

  • 離線處理,將詢問進行分塊,對分塊後的詢問進行操作,通過刪除、增加區間來得到答案。由於我們已經將詢問排序,所以複雜度基本是線性的。

以 HH的項鍊爲例

變量名解釋:

q[i].l/r/id/nmi,q[i].l/r/id/nm 表示第i個詢問所對應的左右端點,詢問的編號以及分塊排序的關鍵字
cnt[i]:icnt[i]:表示顏色爲i的出現的個數

//1.預處理,將詢問分塊
for (int i=1;i<=m;i++){
	scanf("%d %d",&q[i].l,&q[i].r);
	q[i].nm = (q[i].l+len-1)/len;
	q[i].id = i;
}
//排序
bool mycmp(node x,node y){if (x.nm == y.nm) return x.r<y.r;else return x.nm<y.nm;}
sort(q+1,q+m+1,mycmp);
//對區間進行修改
int nowl = 1 , nowr = 0 , ans = 0;
for (int i=1;i<=m;i++){
	while (l<q[i].l) Del(l++);
	while (l>q[i].l) Add(--l);
	while (r<q[i].r) Add(++r);
	while (r>q[i].r) Del(r--);
}//這裏的Del , Add 都是具體的操作,因題而異

六、線段樹:

1)定義及性質:
  • 線段樹也是一種基於二進制而來的一種數據結構,主要用來維護區間上的一些操作。
  • 線段樹的每一個節點都代表着一個區間;線段樹的葉子結點都代表着一個長度爲1的區間。
  • 對於一個節點xx,它的左兒子的編號是x2x*2,右兒子是x2+1x*2+1.
  • 對於一個節點x包含區間[l,r][l,r],它的左兒子包含區間[l,mid],右兒子是[mid+1,r][mid+1,r]. 其中mid=l+r>>1mid = l+r>>1
2)基本函數

變量解釋:

tr[p].l/rptr[p].l/r 表示節點號爲p的點包含的區間
tr[p].sumitr[p].sum 表示節點號爲i的點的區間累加和
tr[p].additr[p].add 表示節點號爲i的點的延遲標記

struct Node{int l,r,sum,add;}

struct Tr{
	Node t[4*N];
	#define sum(x) t[p].sum
	#define l(x) t[p].l
	#define r(x) t[p].r
	#define ls p<<1
	#define rs p<<1|1
	void build(int p,int l,int r){
		l(p) = l , r(p) = r , sum(p) = 0;
		if (l == r) {sum(p) = a[l];return;}//遍歷到葉子結點
		int mid = l+r>>1;
		build(ls,l,mid),build(rs,mid+1,r);
		sum(p) = sum(ls) + sum(rs);
	}
	void spread(int p){
		if (!add(p)) return;
		sum(ls)+=add(p)*(r(ls)-l(ls)+1);
		add(ls)+=add(p);
		sum(rs)+=add(p)*(r(rs)-l(rs)+1);
		add(rs)+=add(p);
		add(p) = 0;//下傳完延遲標記之後清空
	}
	void Change(int p,int l,int r,int v){//給[l,r]都加上v
		if (l<=l(p) && r(p)<=r) {
			sum(p)+=(r(p)-l(p)+1)*v;
			add(p)+=v;
			return;
		}
		spread(p);
		int mid = l(p) + r(p) >> 1;
		if (l<=mid) Change(ls,l,r,v);
		if (r>mid) Change(rs,l,r,v);
		sum(p) = sum(ls)+sum(rs);
	}
	int Ask(int p,int l,int r){//詢問[l,r]的區間和
		if (l<=l(p) && r(p)<=r) return sum(p);
		spread(p);
		int mid = l(p) + r(p) >> 1;
		int ans = 0;
		if (l<=mid) ans+=Ask(ls,l,r);
		if (r>mid) ans+=Ask(rs,l,r);
		return ans;
	}
};//定義到結構體裏面調用更加方便

對於其他的線段樹的變形都是基於這個模板的


七、高精度

1)高精加

int a[Maxx] , b[Maxx];
string s1,s2;
cin>>s1>>s2;
int len1 = s1.size() , len2 = s2.size();
for (int i = 1; i <= s1.size(); i++) a[i] = s1[len1-i]-48;
for (int i = 1; i <= s2.size(); i++) b[i] = s2[len2-i]-48;
int len = max(len1 , len2);
for (int i = 1; i <= len; i++){
	c[i] = a[i]+b[i];
	c[i+1] += c[i]/10;
	c[i]%=10;
}
int k = Maxx-1;
while (!c[k] && k>1) k--;
for (int i = k; i >= 1; i--) cout<<c[i];

2)高精減

int a[Maxx] , b[Maxx];
string s1,s2;
cin>>s1>>s2;
if (s1比s2小) cout<<'-',swap(s1,s2);
for (int i = 1; i <= s1.size(); i++) a[i] = s1[len1-i]-48;
for (int i = 1; i <= s2.size(); i++) b[i] = s2[len2-i]-48;
int len = max(len1 , len2);
for (int i = 1; i <= len; i++){
	c[i]+=a[i]-b[i];
	if (c[i] < 0) c[i]+=10 , c[i+1]-=1;
}

3)高精度乘法

for (int i = 1; i <= len1; i++)
	for (int j = 1; j<= len2; j++){
		c[i+j-1]+=a[i]*b[j];
		c[i+j]+=c[i+j-1]/10;
		c[i+j-1]%=10;
	}

八、線性篩

f[1] = 1;
for (int i = 2; i <= Maxx; i++)
  if (!f[i])
    for (int j = i+i; j <= Maxx; j+=i) f[j] = 1;

九、ST表

f[i][j]i2jf[i][j] 表示以i爲起點後面2^j長度的最值,這裏就設爲最大
1) ST表預處理

for (int i = 1; i<=n; i++) f[i][0] = a[i];
int len = log2(n)+1;
for (int j = 1; j<len; j++)
  for (int i = 1; i<=n-(1<<j)+1; i++)
    f[i][j] = max(f[i][j-1] , f[i+(1<<j-1)][j-1]);

2) ST表詢問

int Ask(int l,int r){//詢問[l,r]的最大值
	int k = log2(r-l+1);
	return max(f[i][k] , f[r-(1<<k)+1][k]);
}

十、快讀

#define gc getchar()
int read(){
    int s = 0 , f = 0;char ch = gc;
	while (ch<'0' || ch>'9') f|=ch == '-' , ch = gc;
	while (ch>='0' && ch<='9') s = s*10+ch-48 , ch = gc;
	return f?-s:s;
}

圖論:

一、最短路

1)Dijkstra 堆優化
typedef pair < int , int > pii;
#define mp make_pair
priority_queue < pii , vector < pii > , greater < pii > >  q;
memset(dis , 20 , sizeof dis);
dis[st] = 0;
q.push(mp(dis[st],st));
while(!q.empty()){
	int v = q.top().first , x = q.top().second;
	q.pop();
	if (vis[x]) continue;
	vis[x] = 1;
	for (int i = linkk[x]; i ; i = e[i].Next)
      if (v+e[i].v < dis[e[i].y]) dis[e[i].y] = v+e[i].v , q.push(mp(dis[e[i].y],e[i].y));
}
2)SPFA
memset(vis,0,sizeof vis);//標記當前點是否在隊列中
memset(dis,20,sizeof dis);
dis[1] = 0  ,vis[1] = 1;
queue < int > q;
q.push(1);
while (!q.empty()){
	int x = q.front();q.pop();
	vis[x] = 0;
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y;
		if (d[x] + e[i].v < d[y]){
			d[y] = d[x] + e[i].y;
			if (!vis[y]) vis[y] = 1 , q.push(y);
		}
	}
}
3) Floyed
for (int k = 1; k<=n; k++)
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= n; j++)
	  f[i][j] = min(f[i][k]+f[k][j],f[i][j]);		

二、最小生成樹

1)Kruscal
bool mycmp(Node x,Node y){return x.v < y.v;}
int getfa(int k){return k == fa[k]?k:fa[k] = getfa(fa[k]);}
for (int i = 1; i <= n; i++) fa[i] = i;
sort(e+1,e+m+1,mycmp);
int ans = 0 , cnt = 0;
for (int i = 1; i <= m; i++){
	int x = getfa(e[i].x) , y = getfa(e[i].y);
	if (x == y) continue;
	ans+=e[i].v;
	fa[x] = y;
	cnt++;
	if (cnt == n-1) break;
}

//prim 不想寫了,思路跟dij差不多


三、最近公共祖先

1)樹上倍增:

思路:

  • 通過dfs記錄每一個點的父親以及深度,從而得到他上面2k2^k輩的點,從而對於兩個點,我們可以不斷的跳倍增來實現,複雜度loglog,而且好理解

變量解釋:
fa[i][j]fa[i][j]表示節點i向上2k2^k輩的父親的編號
d[i]d[i]表示編號爲i的結點的深度

代碼實現:

void dfs(int x,int faa,int dd){
	d[x] = dd;
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y;
		if (y == fa) continue;
		fa[y][0] = x;
		dfs(y,x,dd+1);
	}
}//預處理深度以及父親

void find_fa(){
	for (int j = 1; j <= 25; j++)
	  for (int i = 1; i <= n; i++)
	    fa[i][j] = fa[fa[i][j-1]][j-1];
}//狀態轉移

int lca(int u,int v){
	if (d[u] < d[v]) swap(u,v);
	for (int dd = d[u]-d[v] , i = 0; dd; dd>>=1 , i++)
      if (dd&1) u = fa[u][i];
    if (u == v) return u;
	for (int i = 25; i>=0; i--)
	  if (fa[u][i] != fa[v][i]) u = fa[u][i] , v = fa[v][i];
    return fa[u][0];
}//樹上倍增求lca
2) tarjan:
  • 不會

四、基環樹

1)定義:
基環樹是指點數等於邊數的樹,其實就是環套樹

2)如何求解、找環:
基環樹的核心其實就是找到那個環,只要我們找到環,就可以進行一系列操作。
找環其實很簡單,我們只要遍歷一遍,發現遍歷到遍歷到的點時,就可以通過跳躍父節點把環找出來

3)代碼實現

void find_root(int x,int faa){
	if (Flag) return;//找到環就退出
	vis[x] = 1;
	for (int i = linkk[x]; i; i=e[i].Next){
		int y = e[i].y;
		if (y == faa) continue;
		if (Flag) return;//這裏需要加一步,否則會炸掉
		if (vis[y]){
			Flag = 1;
			rt[++cnt] = y;
			for (int j = x; j != y; j = fa[j]) rt[++cnt] = j;
			return;
		}
		else fa[y] = x,find_root(y,x);
	}
}

五、有向圖的拓撲序

1)用途:
1.有向圖的拓撲序常常可以用來解決有向圖的一些狀態轉移,所以常用有向圖的拓撲將有向圖轉換成數列來進行dp
3.同時有向圖的拓撲序還可以判環

2)方法:
找到入度爲0的點加入隊列,刪除它相連的所有邊,繼續將入度爲0的點加入隊列,繼續刪……重複這一操作。
如果剩餘還有點存在,那麼他們一定是環

3)代碼實現:

int cnt = 0;
queue < int > q;
for (int i = 1; i <= n; i++)
  if (!in[i]) q.push(i);
while (!q.empty()){
	cnt++;
	int x = q.front();q.pop();
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y;
		in[y]--;
		if (!in[y]) q.push(y);
	}
}
if (cnt == n) 無環
else 有環

六、圖的割點與割邊

割邊:

定義:對於eE,e\in E,,在途中刪去邊ee後,若圖GG分裂成了兩個不相連的子圖,則稱eeGG的割邊或橋

1)變量解釋:
dfn[x]x()dfn[x]表示編號爲x的結點遍歷到的次序(時間戳)
low[x]xlow[x]表示編號爲x的點能通過其他邊返回的最早的祖先(最先被遍歷到的)

由此我們可以得到,當dfn[x]<low[y]dfn[x]<low[y]時,說明當前點不能通過這條邊走到更早的邊,也就是說這條邊就是割邊

2)代碼實現:

//len 初值爲1
void Tarjan(int x,int laste){
	dfn[x] = low[x] = ++cnt;//給結點的遍歷順序編個號(其實就是dfs序)
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y;
		if (!dfn[y]){
			Tarjan(y,i);
			low[x] = min(low[x],low[y]);
			if (low[y] > dfn[x]) isb[i] = isb[i^1] = 1;//說明是割邊
		}
		else low[x] = min(low[x],dfn[y]);
	}
}
割點

大體與割邊的思想相同,只不過當dfn[x]<=low[y]dfn[x]<=low[y]時就爲割點,因爲此時當前點是被包含的,刪數仍然不行

代碼實現

void Tarjan(int x){
	int Flag = 0;
	dfn[x] = low[x] = ++cnt;
	for (int i = linkk[x]; i; i = e[i].Next){
		int y =e[i].y;
		if (!dfn[y]){
			Tarjan(y);
			low[x] = min(low[x] , low[y]);
			if (low[y] >= dfn[x]){
				Flag++;
				if (x != Root || Flag > 1) isc[x] = 1;//如果根節點的出邊爲1,那麼便不是割點
			}
		}
		else low[x] = min(low[x] , dfn[y]);
	}
}

七、二分圖

1)定義:

如果一張無向圖的NN個節點(N>=2N>=2)可以分成AA,BB兩個集合,是在同一個集合內的點沒有邊相連,那麼稱這一張無向圖爲二分圖

  • 無向圖上的基本術語/概念

一、二分圖的匹配:選取二分圖中的邊集SS,當且僅當集合中的任意兩條邊都沒有共同公共端點時,S爲二分圖的一個匹配
二、匹配邊/未匹配邊:在集合S中的邊稱爲匹配邊,除此之外的邊稱爲未匹配邊
三、未蓋點(未匹配點)/匹配點:匹配邊相連的端點稱爲匹配點,除此之外的稱爲未蓋點(未匹配點)
四、交錯路(增廣路):任意兩條相鄰的邊一定是一條匹配邊另一條爲匹配邊的路稱爲交錯路
五、可增廣路:端點爲未蓋點的交錯路(增光路)稱爲可增廣路
六、可增廣路的性質

  • 長度lenlen爲奇數(兩端都要是非匹配點)
  • 1,35len1,3,5,……len條邊都是非匹配邊,而2,4,6len12,4,6……len-1都是匹配邊,也就是說,我們只要把狀態反一反,就可以使匹配數+1
  • 所以我們得到以下結論:若二分圖的匹配S使最大匹配,當且僅當S不是增廣路

二、二分圖的最大匹配

  • 二分圖匹配其實就是找增廣路的個數然後將他變成可增廣路使得匹配數+1

這裏需要介紹匈牙利算法(增廣路算法):
1、設集合SS爲空,即此時一條匹配邊都沒有
2、找出圖中的增廣路,將邊取反,即匹配邊變成非匹配邊,非匹配邊變成匹配邊,此時匹配個數便會在原基礎上+1
那麼這裏的關鍵就是如何求增廣路。
當我們想匹配(x,y)時,滿足以下條件之一的時候,易證找出了一條增廣路:
1、y本身便是非匹配點,此時直接可以連邊組成長度爲1的增廣路
2、y已經連了xx',但xx'可以匹配到另一個新的點yy',那麼此時便可以讓xx'去匹配yy',yyxx便可以匹配
dfsdfs便可解決。

但匈牙利更是一個基於貪心的算法,它認爲:當一個點已經成爲匹配點時,只會找到增廣路使總的匹配數增多,而不會使當前點成爲非匹配點。

顯然是正確的(其實我也不是到爲什麼是正確的

代碼實現:

bool dfs(int x){
	for (int i=linkk[x];i;i=e[i].Next){
		int y = e[i].y;
		if (vis[y]) continue;
		vis[y] = 1;
		if (!match[y] || dfs(match[y])) {
		    match[y] = x;match[x] = y;return 1;
		}
	}
	return 0;

    for (int i=1;i<=n;i++) memset(vis,0,sizeof vis),ans+=dfs(i);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章