知识点的模板整理与复习

模板整理——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]:不用解释 a[i]:不用解释
L [ i ] : 第 i 个块的左端点 L[i]:第i个块的左端点 L[i]:i个块的左端点
R [ i ] : 第 i 个块的右端点 R[i]:第i个块的右端点 R[i]:i个块的右端点
p o s [ i ] : 第 i 个点所属于的块的编号 pos[i]:第i个点所属于的块的编号 pos[i]:i个点所属于的块的编号
s u m [ i ] : 第 i 块的累加和 sum[i]:第i块的累加和 sum[i]:i块的累加和
a d d [ i ] : 第 i 块的增量 add[i]:第i块的增量 add[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 / i d / n m 表示第 i 个询问所对应的左右端点 , 询问的编号以及分块排序的关键字 q[i].l/r/id/nm 表示第i个询问所对应的左右端点,询问的编号以及分块排序的关键字 q[i].l/r/id/nm表示第i个询问所对应的左右端点,询问的编号以及分块排序的关键字
c n t [ i ] : 表示颜色为 i 的出现的个数 cnt[i]:表示颜色为i的出现的个数 cnt[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的区间。
  • 对于一个节点 x x x,它的左儿子的编号是 x ∗ 2 x*2 x2,右儿子是 x ∗ 2 + 1 x*2+1 x2+1.
  • 对于一个节点x包含区间 [ l , r ] [l,r] [l,r],它的左儿子包含区间[l,mid],右儿子是 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]. 其中 m i d = l + r > > 1 mid = l+r>>1 mid=l+r>>1
2)基本函数

变量解释:

t r [ p ] . l / r 表示节点号为 p 的点包含的区间 tr[p].l/r 表示节点号为p的点包含的区间 tr[p].l/r表示节点号为p的点包含的区间
t r [ p ] . s u m 表示节点号为 i 的点的区间累加和 tr[p].sum 表示节点号为i的点的区间累加和 tr[p].sum表示节点号为i的点的区间累加和
t r [ p ] . a d d 表示节点号为 i 的点的延迟标记 tr[p].add 表示节点号为i的点的延迟标记 tr[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 ] 表示以 i 为起点后面 2 j 长度的最值 f[i][j] 表示以i为起点后面2^j长度的最值 f[i][j]表示以i为起点后面2j长度的最值,这里就设为最大
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;
}
2)堆优化prim
#include<bits/stdc++.h>
using namespace std;

const int N = 2e5+100;
typedef pair < int , int > pii;
priority_queue < pii , vector < pii > , greater < pii > > q;
int n,m;
struct Node{
	int y,Next,v;
}e[2*N];
int len,linkk[N];
int dis[N],ans = 0;
bool vis[N];

void Insert(int x,int y,int z){
	e[++len] = (Node){y,linkk[x],z};
	linkk[x] = len;
}

int main(){
	scanf("%d %d",&n,&m);
	for (int i = 1,x,y,z; i <= m; i++) scanf("%d %d %d",&x,&y,&z) , Insert(x,y,z) , Insert(y,x,z);
	memset(dis,20,sizeof dis);
	q.push({dis[1] = 0,1});
	while (q.size()){
		int x = q.top().second; q.pop();
		if (vis[x]) continue; vis[x] = 1; ans+=dis[x];
		for (int i = linkk[x]; i; i = e[i].Next){
			int y = e[i].y , v = e[i].v;
			if (v < dis[y]) q.push({dis[y] = v,y});
		}
	}
	cout<<ans;
} 
3)B开头的算法
#include<bits/stdc++.h>
using namespace std;

const int N = 6e3+10,M = 2e5+10;
int n,m;
struct Node{
	int y,Next,v;
}e[2*M];
int len , linkk[N];
int Min[N] , mat[N] , fa[N];

void Insert(int x,int y,int v){
	e[++len] = (Node){y,linkk[x],v};
	linkk[x] = len;
}

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

int main(){
	scanf("%d %d",&n,&m);
	for (int i = 1,x,y,v; i <= m; i++)
	  scanf("%d %d %d",&x,&y,&v) , Insert(x,y,v) ,  Insert(y,x,v);
	for (int i = 1; i <= n; i++) fa[i] = i;
	int sum = 0;
	while (1){
		bool f = 1;
		for (int i = 1; i <= n; i++) Min[i] = 1e9+7;
		for (int i = 1; i <= n; i++)
		  for (int j = linkk[i]; j; j = e[j].Next){
		  	  int x = getfa(i) , y = getfa(e[j].y); if (x == y) continue;
		  	  if (e[j].v < Min[x]) Min[x] = e[j].v , mat[x] = y;
		  }
		for (int i = 1; i <= n; i++)
		  if (Min[i]<1e9+7 && getfa(i)!=getfa(mat[i])){
		  	  sum+=Min[i]; fa[getfa(i)] = getfa(mat[i]); f = 0;
		  }
		if (f) break;
 	}
 	printf("%d\n",sum);
 	return 0;
}

三、最近公共祖先

1)树上倍增:

思路:

  • 通过dfs记录每一个点的父亲以及深度,从而得到他上面 2 k 2^k 2k辈的点,从而对于两个点,我们可以不断的跳倍增来实现,复杂度 l o g log log,而且好理解

变量解释:
f a [ i ] [ j ] fa[i][j] fa[i][j]表示节点i向上 2 k 2^k 2k辈的父亲的编号
d [ i ] 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 有环

六、图的割点与割边

割边:

定义:对于 e ∈ E , e\in E, eE,,在途中删去边 e e e后,若图 G G G分裂成了两个不相连的子图,则称 e e e G G G的割边或桥

1)变量解释:
d f n [ x ] 表示编号为 x 的结点遍历到的次序 ( 时间戳 ) dfn[x]表示编号为x的结点遍历到的次序(时间戳) dfn[x]表示编号为x的结点遍历到的次序(时间戳)
l o w [ x ] 表示编号为 x 的点能通过其他边返回的最早的祖先(最先被遍历到的) low[x]表示编号为x的点能通过其他边返回的最早的祖先(最先被遍历到的) low[x]表示编号为x的点能通过其他边返回的最早的祖先(最先被遍历到的)

由此我们可以得到,当 d f n [ x ] < l o w [ y ] 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]);
	}
}
割点

大体与割边的思想相同,只不过当 d f n [ x ] < = l o w [ 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)定义:

如果一张无向图的 N N N个节点( N > = 2 N>=2 N>=2)可以分成 A A A, B B B两个集合,是在同一个集合内的点没有边相连,那么称这一张无向图为二分图

  • 无向图上的基本术语/概念

一、二分图的匹配:选取二分图中的边集 S S S,当且仅当集合中的任意两条边都没有共同公共端点时,S为二分图的一个匹配
二、匹配边/未匹配边:在集合S中的边称为匹配边,除此之外的边称为未匹配边
三、未盖点(未匹配点)/匹配点:匹配边相连的端点称为匹配点,除此之外的称为未盖点(未匹配点)
四、交错路(增广路):任意两条相邻的边一定是一条匹配边另一条为匹配边的路称为交错路
五、可增广路:端点为未盖点的交错路(增光路)称为可增广路
六、可增广路的性质

  • 长度 l e n len len为奇数(两端都要是非匹配点)
  • 1 , 3 , 5 , … … l e n 1,3,5,……len 1,35……len条边都是非匹配边,而 2 , 4 , 6 … … l e n − 1 2,4,6……len-1 2,4,6……len1都是匹配边,也就是说,我们只要把状态反一反,就可以使匹配数+1
  • 所以我们得到以下结论:若二分图的匹配S使最大匹配,当且仅当S不是增广路

二、二分图的最大匹配

  • 二分图匹配其实就是找增广路的个数然后将他变成可增广路使得匹配数+1

这里需要介绍匈牙利算法(增广路算法):
1、设集合 S S S为空,即此时一条匹配边都没有
2、找出图中的增广路,将边取反,即匹配边变成非匹配边,非匹配边变成匹配边,此时匹配个数便会在原基础上+1
那么这里的关键就是如何求增广路。
当我们想匹配(x,y)时,满足以下条件之一的时候,易证找出了一条增广路:
1、y本身便是非匹配点,此时直接可以连边组成长度为1的增广路
2、y已经连了 x ′ x' x,但 x ′ x' x可以匹配到另一个新的点 y ′ y' y,那么此时便可以让 x ′ x' x去匹配 y ′ y' y, y y y x x x便可以匹配
d f s dfs dfs便可解决。

但匈牙利更是一个基于贪心的算法,它认为:当一个点已经成为匹配点时,只会找到增广路使总的匹配数增多,而不会使当前点成为非匹配点。

显然是正确的(其实我也不是到为什么是正确的

代码实现:

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);
}

二分图的判定

void dfs(int x){
	if (col[x] == 1) num[cnt]++;
	if (col[x] == 2) num[cnt]--;
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y;
		if (col[y] == 0) col[y] = 3-col[x],dfs(y);
		else if (col[y] == col[x]) {fla = 1;}
	}
	return;
}

差分约束

差分约束的一句话总结:

在这里插入图片描述
同时差分约束需要用 S P F A SPFA SPFA解决


质数

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5+10;
bool ish[N];
int n;

int main(){
	ish[1] = 1;//1不能算素数
	scanf("%d",&n);
	for (int i = 2; i <= n; i++)
  	  if (!ish[i])
    	for (int j = i; j <= n/i; j++) ish[i*j] = 1;//这一维可以从i开始
    for (int i = 1; i <= n; i++)
      if (!ish[i]) cout<<i<<' ';
}   爱是筛法

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5+10;
int v[N];
vector < int > pr;
int n;

int main(){
	scanf("%d",&n);
	for (int i = 2; i <= n; i++){
		if (v[i] == 0) {v[i] = i; pr.push_back(i);}
		for (int j = 0; j < pr.size(); j++){
			if (pr[j]>v[i] || pr[j]>n/i) break;
			v[i*pr[j]] = pr[j];
		}
	}
	for (int i = 0; i < pr.size(); i++) cout<<pr[i]<<' ';cout<<endl;
}    线性筛法

树上启发式合并

一般用来求字数内颜色编号最多或者最大之类的问题。
对于每一个节点都有一种类似于身份的东西
关于思路

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5+10;
int n,m,ty,la;
struct Node{
	int y,Next;
}e[2*N];
int len , linkk[N];
struct node{
	int x,y,v;
}E[N];
int V[N];
int c[N],fa[N*2];
int tot;
int cnt[N],ans[N];
int maxx = 0 , maxn = 1e9;
int logn;

bool mycmp(node x,node y){return x.v < y.v;}

int get(int x){return fa[x] == x?x:fa[x] = get(fa[x]);}

int siz[N] , Fa[N][30] , son[N];
void Dfs(int x,int faa){
	int Max = 0;
	siz[x] = 1; Fa[x][0] = faa;
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y; if (y == faa) continue;
		Dfs(y,x);
		siz[x]+=siz[y];
		if (Max < siz[y]) Max = siz[son[x] = y];
	}
}

void find_fa(){
	for (int i = 1; i < 30; i++) for (int j = 1; j <= tot; j++) Fa[j][i] = Fa[Fa[j][i-1]][i-1];
}

void Calc(int x,int faa,int he){
	if (c[x]!=0) cnt[c[x]]++;
	if (cnt[c[x]] > maxx) maxx = cnt[c[x]] , maxn = c[x];
	else if (cnt[c[x]] == maxx) maxn = min(maxn,c[x]);
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y; if (y == faa || y == he) continue;
		Calc(y,x,he);
	}
}

void Back(int x,int faa,int he){
	cnt[c[x]]--;
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y; if (y == faa || y == he) continue;
		Back(y,x,he);
	}
}

void Dsu(int x,int faa,int op){
	int he = son[x];
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y; if (y == faa || y == he) continue;
		Dsu(y,x,0);
	}
	if (he) Dsu(he,x,1);
	Calc(x,faa,he); /*cout<<"x = "<<x<<' '<<maxx<<' '<<maxn<<endl;*/
	ans[x] = maxn;
	if (op == 0) Back(x,faa,0) , maxx = maxn = 0;
}

int Find(int x,int y){
	for (int i = 29; i >= 0; i--)
	  if (Fa[x][i] && V[Fa[x][i]] <= y) x = Fa[x][i];
	return ans[x];
}

void Insert(int x,int y){
	e[++len] = (Node){y,linkk[x]};
	linkk[x] = len;
}

int main(){
	freopen("garden.in","r",stdin);
	freopen("garden.out","w",stdout);
	scanf("%d %d %d",&n,&m,&ty);
	for (int i = 1; i <= n; i++) scanf("%d",&c[i]);
	for (int i = 1; i <= m; i++) scanf("%d %d %d",&E[i].x,&E[i].y,&E[i].v);
	sort(E+1,E+m+1,mycmp);
	for (int i = 1; i <= 2*n; i++) fa[i] = i; tot = n;
	for (int i = 1; i <= m; i++){
		int x = get(E[i].x) , y = get(E[i].y); if (x == y) continue;
		fa[x] = fa[y] = ++tot; Insert(x,tot) , Insert(y,tot) , Insert(tot,x) , Insert(tot,y); V[tot] = E[i].v;
	}
	for (int i = 1; i <= tot; i++) if (fa[i] == i) Dfs(i,0); find_fa();
	for (int i = 1; i <= tot; i++) if (fa[i] == i) Dsu(i,0,0);
	int q; scanf("%d",&q);
	while (q--){
		int x,y; scanf("%d %d",&x,&y);
		if (ty == 2) x^=la , y^=la;
		la = Find(x,y); printf("%d\n",la);
	}
}

回滚莫队

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int N = 2e5+10;

int n;
int coo[N] , co[N];
struct Node{
	int y,Next;
}e[2*N];
int LL[N] , RR[N] , bel[N],L[N] , R[N],cntt[N];
int Cnt[N];
int maxx = 0 , maxn = 0 , sum = 0;
struct qujian{
	int l,r,id;
}q[2*N];
int len , linkk[N];
int ans[N];
int op[N]; 

void Insert(int x,int y){
	e[++len] =  (Node){y,linkk[x]};
	linkk[x] = len;
}
int cnt,hom[N];
void dfs(int x,int faa){
	L[x] = ++cnt;
	hom[L[x]] = x;
	for (int i = linkk[x]; i; i = e[i].Next)
	  if (e[i].y != faa) dfs(e[i].y,x);
	R[x] = cnt;
}

bool mycmp(qujian x,qujian y){
	if (bel[x.l] == bel[y.l]) return x.r < y.r;
	return bel[x.l] < bel[y.l];
}

void Add(int x,int &Maxx,int &Sum){
	++Cnt[co[x]];
	if (Cnt[co[x]] > Maxx) Maxx = Cnt[co[x]] , Sum = co[x];
	else if (Cnt[co[x]] == Maxx) Sum+=co[x];
}

void Del(int x){
	--Cnt[co[x]];
}

void work(int ll,int rr,int x){
	for (int i = ll; i <= rr; i++) cntt[co[i]] = 0;
	int Maxx = 0 ,Sum = 0;
	for (int i = ll; i <= rr; i++){
		++cntt[co[i]];
		if (cntt[co[i]] > Maxx) Maxx = cntt[co[i]] , Sum = co[i];
		else if (cntt[co[i]] == Maxx) Sum+=co[i];
	}
	ans[q[x].id] = Sum;
}

signed main(){
	scanf("%lld",&n);
	for (int i = 1; i <= n; i++) scanf("%lld",&coo[i]);
	for (int i = 1,x,y; i < n; i++)
	  scanf("%lld %lld",&x,&y) , Insert(x,y) , Insert(y,x);
	dfs(1,0);
	for (int i = 1; i <= n; i++) co[L[i]] = coo[i];//dfs序之后的颜色
//    **分块过程**
	int lenn = sqrt(n);
	int Siz = n/lenn; 
	for (int i = 1; i <= Siz; i++){
	    if (i*lenn > n) break;
		LL[i] = RR[i-1] + 1 , RR[i] = i*lenn;
	}
	if (RR[Siz] < n) LL[++Siz] = RR[Siz-1] + 1 , RR[Siz] = n;
	for (int i = 1; i <= Siz; i++)
	  for (int j = LL[i]; j <= RR[i]; j++) bel[j] = i;
//	  **End**

//   **询问离线**
	int Len = sqrt(n);
	for (int i = 1; i <= n; i++){
		q[i].l = L[i] , q[i].r = R[i];
		q[i].id = i;
	}
	sort(q+1,q+n+1,mycmp);
	// **End**

//   **回滚莫队主程序**
	int la = 0 , l = 1 , r = 0;
	for (int i = 1; i <= n; i++){
		if (bel[q[i].l] == bel[q[i].r]){
			int Maxx = 0 , Sum = 0;
			work(q[i].l,q[i].r,i);
			continue;
		}//处于同一块的问题直接暴力
		if (la != bel[q[i].l]){//出现了新的块,进行初始化
			la = bel[q[i].l];
			while (r > RR[la]) Del(r--);
			while (l < RR[la] + 1) Del(l++);
			maxx = sum = 0;
		}
		while (r < q[i].r) Add(++r,maxx,sum);//右端点单调递增
		int maxxx = maxx , summ = sum , ll = l;
	//注意赋一个新的值,避免左端点对当前的值造成影响,不然回滚就失去了意义
		while (ll > q[i].l) Add(--ll,maxxx,summ);//将左端点的答案加进去
		while (ll < l) Del(ll++);//回滚
		ans[q[i].id] = summ;
	}
	//**End**
	for (int i = 1; i <= n; i++)
	  printf("%lld ",ans[i]);
}

树链剖分

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int N = 2e5+10;

int n,m;
struct Node{
	int y,Next;
}e[2*N];
int len = 0 , linkk[N];

struct Tr{
	int tr[N];
	#define lowbit(x) (x&(-x))
	int Ask(int x){int sum = 0; for (int i = x; i; i -= lowbit(i)) sum+=tr[i];return sum;}
	void Change(int x,int v){for (int i = x; i <= n; i+=lowbit(i)) tr[i]+=v;}
}tr;

struct qq{
	int x,y,lca;
}q[N];

int d[N] , fa[N][30];
int num[N] , cnt = 0;
int son[N] , siz[N] , top[N];

void Insert(int x,int y){
	e[++len].Next = linkk[x];
	linkk[x] = len;
	e[len].y = y;
}

void dfs1(int x,int dd,int faa){
	int Max = 0;
	d[x] = dd;
	fa[x][0] = faa;
	siz[x] = 1;
	for (int i = linkk[x]; i; i = e[i].Next){
	    int y = e[i].y;
	    if (y == faa) continue;
		dfs1(e[i].y,dd+1,x);
		siz[x]+=siz[y];
		if (Max < siz[y]) Max = siz[son[x] = y];
	}
}

void dfs2(int x,int id){
	num[x] = ++cnt; top[x] = id;
	if (son[x]) dfs2(son[x] , id);
	for (int i = linkk[x]; i; i = e[i].Next){
		int y = e[i].y;
		if (y == fa[x][0] || y == son[x]) continue;
		dfs2(y,y);
	}
}

void find_fa(){
	for (int i = 1; i < 30; i++)
	  for (int j = 1; j <= n; j++)
	    if (fa[j][i-1] == 0) fa[j][i] = 0;
	    else fa[j][i] = fa[fa[j][i-1]][i-1];
}

int Lca(int x,int y){
	if (d[x] < d[y]) swap(x,y);
	for (int dd = d[x] - d[y] , i = 0; dd; dd>>=1 , i++)
	  if (dd&1) x = fa[x][i];
	if (x == y) return x;
	for (int i = 29; i >= 0; i--)
	  if (fa[x][i] != fa[y][i]) x = fa[x][i] , y = fa[y][i];
	return fa[x][0];
}

bool mycmp(qq x,qq y){
	return d[x.lca] > d[y.lca];
}

int Ask(int x,int y){
	int ans = 0;
	while (top[x] != top[y]){
		if (d[top[x]] < d[top[y]]) swap(x,y);
		ans+=tr.Ask(num[x]) - tr.Ask(num[top[x]] - 1);
		x = fa[top[x]][0];
	}
	if (d[x] < d[y]) swap(x,y);
	ans+=tr.Ask(num[x]) - tr.Ask(num[y]-1);
	return ans;
}

signed main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%lld %lld",&n,&m);
	for (int i = 1,x,y; i < n; i++)
	  scanf("%lld %lld",&x,&y) , Insert(x,y) , Insert(y,x);
	dfs1(1,0,0);
	dfs2(1,0);
	find_fa();
	for (int i = 1; i <= m; i++)
	  scanf("%lld %lld",&q[i].x,&q[i].y) , q[i].lca = Lca(q[i].x,q[i].y);
	sort(q+1,q+m+1,mycmp);
	int ans = 0;
	for (int i = 1; i <= m; i++){
		ans+=Ask(q[i].x,q[i].y);
		tr.Change(num[q[i].lca],1);
	}
	printf("%lld",ans);
	return 0;
}


  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值