算法总复习

STL

1.vector动态数组

👉1.vector动态数组👈

👉2.stack栈👈

👉3.queue队列👈

👉4.deque双端队列👈

👉5.priority_queue优先队列👈

👉6.map映射👈

👉7.set集合👈

👉8.bitset👈

👉9.pair二元组👈

👉10.tuple元组👈

KMP

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

string s, t;
int next[1010], l1, l2;

void get(string s){
	int j = 0, k = -1;
	next[0] = -1;
	while(j < l2){
		if(k == -1 || t[j] == t[k])
			next[++j] = ++k;
		else
			k = next[k];
	}
	
}

int kmp(string s, string t, int pos){
	int j = 0, i = pos;
	while(i < l1 && j < l2){
		if(j == -1 || s[i] == t[j])
			i++, j++;
		else
			j = next[j];
	}
	if(j >= l2)
		return i - l2 + 1;
	else
		return -1;
}

int main(){
	cin >> s >> t;
	l1 = s.size();
	l2 = t.size();
	cout <<	kmp(s, t, 0) << endl;
	return 0;
}

高精

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

string s1, s2;
int a[10010], b[10010], c[10010];
void init(int a[], string str1){
	int l = str1.size();
	for(int i = 1; i <= l; ++i)
		a[i] = str1[l - i] - '0';
	a[0] = l;
}

void pd(string &str1, string &str2){
	if((str1 < str2 && str1.size() == str2.size()) || str1.size() < str2.size()){
		swap(str1, str2);
	 	cout << "-";
	}
}

int sub(int z[], int x[], int y[]){
	int len = x[0];
	for(int i = 1; i <= len; i++){
		if(x[i] < y[i]){
			x[i] += 10;
			x[i + 1]--;
		}
		z[i] = x[i]-y[i];
	}
	while(len > 1 && z[len] == 0)
		len--;
	z[0] = len;
}

int add(int z[],int x[],int y[]){
	int g = 0;
	int len = max(x[0],y[0]);
	for(int i = 1; i <= len; i++){
		z[i] = x[i] + y[i] + g;
		g = z[i] / 10;
		z[i] %= 10;
	}
	if(g > 0)
		z[++len] = g;
	z[0] = len;
}

void mul(int z[],int x[],int y[]){
	int lz = x[0] + y[0] - 1;
	for(int i = 1; i <= x[0]; i++){
		for(int j = 1; j <= y[0]; j++){
			z[i + j - 1] += x[i] * y[j];
			z[i + j] += z[i + j - 1] / 10;
			z[i + j - 1] %= 10;
		}
	}
	if(z[lz + 1]!=0)
		lz++;
	while(lz > 1 && z[lz] == 0)
		lz--;
	z[0] = lz;
}

int main(){
	cin >> s1 >> s2;
	memset(a, 0, sizeof(a));
	memset(b, 0, sizeof(b));
	memset(c, 0, sizeof(c));
	init(a, s1);
	init(b, s2);
	mul(c, a, b);
	for(int i = c[0]; i > 0; i--)
			cout << c[i];
	return 0;
}

并查集

【算法与数据结构】—— 并查集_酱懵静的博客-CSDN博客_并查集

const int  N=1005					//指定并查集所能包含元素的个数(由题意决定)
int pre[N];     					//存储每个结点的前驱结点 
int rank[N];    					//树的高度 
void init(int n)     				//初始化函数,对录入的 n个结点进行初始化 
{
    for(int i = 0; i < n; i++){
        pre[i] = i;     			//每个结点的上级都是自己 
        rank[i] = 1;    			//每个结点构成的树的高度为 1 
    } 
}
int find(int x)     	 		    //查找结点 x的根结点 
{
    if(pre[x] == x) return x;  		//递归出口:x的上级为 x本身,则 x为根结点 
    return find(pre[x]); 			//递归查找 
} 
 
int find(int x)     				//改进查找算法:完成路径压缩,将 x的上级直接变为根结点,那么树的高度就会大大降低 
{
    if(pre[x] == x) return x;		//递归出口:x的上级为 x本身,即 x为根结点 
    return pre[x] = find(pre[x]);   //此代码相当于先找到根结点 rootx,然后 pre[x]=rootx 
} 

bool isSame(int x, int y)      		//判断两个结点是否连通 
{
    return find(x) == find(y);  	//判断两个结点的根结点(即代表元)是否相同 
}

bool join(int x,int y)
{
    x = find(x);						//寻找 x的代表元
    y = find(y);						//寻找 y的代表元
    if(x == y) return false;			//如果 x和 y的代表元一致,说明他们共属同一集合,则不需要合并,返回 false,表示合并失败;否则,执行下面的逻辑
    if(rank[x] > rank[y]) pre[y]=x;		//如果 x的高度大于 y,则令 y的上级为 x
    else								//否则
    {
        if(rank[x]==rank[y]) rank[y]++;	//如果 x的高度和 y的高度相同,则令 y的高度加1
        pre[x]=y;						//让 x的上级为 y
	}
	return true;						//返回 true,表示合并成功
}

并查集的主要用途有以下两点:
1、维护无向图的连通性(判断两个点是否在同一连通块内,或增加一条边后是否会产生环);
2、用在求解最小生成树的Kruskal算法里。

Kruskal

#include<bits/stdc++.h>	//POJ不支持
 
#define rep(i,a,n) for (int i=a;i<=n;i++)//i为循环变量,a为初始值,n为界限值,递增
#define per(i,a,n) for (int i=a;i>=n;i--)//i为循环变量, a为初始值,n为界限值,递减。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair
 
using namespace std;
 
const int inf = 0x3f3f3f3f;//无穷大
const int maxn = 1e5;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll>  pll;
typedef pair<int, int> pii;
//*******************************分割线,以上为自定义代码模板***************************************//
 
struct edge{
	int s;//边的起始顶点。
	int e;//边的终端顶点。
	int w;//边权值。
	bool operator < (const edge &a){
		return w<a.w;
	}
};
bool cmp(edge x, edge y){
	return x.w < y.w ? true : false;
}
edge temp[maxn];//临时数组存储边。
int verx[maxn];//辅助数组,判断是否连通。
edge tree[maxn];//最小生成树。
int n,m;//n*n的图,m条边。
int cnt;//统计生成结点个数,若不满足n个,则生成失败。
int sum;//最小生成树权值总和。
void print(){
	//打印最小生成树函数。
	cout<<"最小生成树的权值总和为:"<<sum<<endl;
	rep(i,0,cnt-1){
		cout<<tree[i].s<<" "<<tree[i].e<<"边权值为"<<tree[i].w<<endl;
	}
}
void Kruskal(){
	rep(i,1,n)
		verx[i]=i;//这里表示各顶点自成一个连通分量。
	cnt=0;sum=0;
	sort(temp,temp+m, cmp);//将边按权值排列。
	int v1,v2;
	rep(i,0,m-1){
		v1=verx[temp[i].s];
		v2=verx[temp[i].e];
		if(v1!=v2){
			tree[cnt].s=temp[i].s;tree[cnt].e=temp[i].e;tree[cnt].w=temp[i].w;//并入最小生成树。
			rep(j,1,n){
				//合并v1和v2的两个分量,即两个集合统一编号。
				if(verx[j]==v2)verx[j]=v1; //默认集合编号为v2的改为v1.
			}
			sum+=tree[cnt].w;
			cnt++;
		}
	}
	//结束双层for循环之后得到tree即是最小生成树。
	print();
}
int main(){
	//freopen("in.txt", "r", stdin);//提交的时候要注释掉
	IOS;
	while(cin>>n>>m){
		int u,v,w;
		rep(i,0,m-1){
			cin>>u>>v>>w;
			temp[i].s=u;temp[i].e=v;temp[i].w=w;
		}
		Kruskal();
	}
	return 0;
}
 

djikstra

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

int ans[100010], head[100010], vis[100010], cnt;
int m, n, s;

struct e{
	int to, next, w;
}e[1000010];

struct node{
	int num;
	int id;
	bool operator < (const node &x) const{
		return x.num < num;
	}
};

void add(int u, int v, int z){
	e[++cnt].to = v;
	e[cnt].next = head[u];
	e[cnt].w = z;
	head[u] = cnt;
}

priority_queue <node> q;

int main(){
	cin >> n >> m >> s;
	for(int i = 1; i <= n; ++i)
		ans[i] = 1e9;
	ans[s] = 0;
	for(int i = 1; i <= m; i++){
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
	}
	q.push((node) {0, s});
	while(!q.empty()){
		node t = q.top();
		q.pop();
		int x = t.id;
		if(!vis[x]){
			vis[x] = 1;
			for(int i = head[x]; i != 0; i = e[i].next){
				int v = e[i].to;
				if(ans[v] > ans[x] + e[i].w){
					ans[v] = ans[x] + e[i].w;
					if(!vis[v])
						q.push((node) {ans[v], v});
				}
			}
		}
	}
	for(int i = 1; i <= n; i++)
		cout << ans[i] << " ";
	return 0;
}

Prim

void prim()
{
    dist[1] = 0;//把点1加入集合S,点1在集合S中,将它到集合的距离初始化为0
    book[1] = true;//表示点1已经加入到了S集合中
    for(int i = 2 ; i <= n ;i++)dist[i] = min(dist[i],g[1][i]);//用点1去更新dist[]
    for(int i = 2 ; i <= n ; i++)
    {
        int temp = INF;//初始化距离
        int t = -1;//接下来去寻找离集合S最近的点加入到集合中,用t记录这个点的下标。
        for(int j = 2 ; j <= n; j++)
        {
            if(!book[j]&&dist[j]<temp)//如果这个点没有加入集合S,而且这个点到集合的距离小于temp就将下标赋给t
            {
                temp = dist[j];//更新集合V到集合S的最小值
                t = j;//把点赋给t
            }
        }
        if(t==-1){res = INF ; return ;}
        //如果t==-1,意味着在集合V找不到边连向集合S,生成树构建失败,将res赋值正无穷表示构建失败,结束函数
        book[t] = true;//如果找到了这个点,就把它加入集合S
        res+=dist[t];//加上这个点到集合S的距离
        for(int j = 2 ; j <= n ; j++)dist[j] = min(dist[j],g[t][j]);//用新加入的点更新dist[]
    }
}
int main()
{
    cin>>n>>m;//读入这个图的点数n和边数m
    for(int i = 1 ; i<= n ;i++)
    {
        for(int j = 1; j <= n ;j++)
        {
            g[i][j] = INF;//初始化任意两个点之间的距离为正无穷(表示这两个点之间没有边)
        }
        dist[i] = INF;//初始化所有点到集合S的距离都是正无穷
    }
    for(int i = 1; i <= m ; i++)
    {
        int a,b,w;
        cin>>a>>b>>w;//读入a,b两个点之间的边
        g[a][b] = g[b][a] = w;//由于是无向边,我们对g[a][b]和g[b][a]都要赋值
    }
    prim();//调用prim函数
    if(res==INF)//如果res的值是正无穷,表示不能该图不能转化成一棵树,输出orz
        cout<<"orz";
    else
        cout<<res;//否则就输出结果res
    return 0;
}

快速幂

b^{11} = b ^{8} * b^{2} * b^{1}

int quickPower(int a, int b)//是求a的b次方
{
	int ans = 1, base = a;//ans为答案,base为a^(2^n)
	while(b > 0)//b是一个变化的二进制数,如果还没有用完
    {
		if(b & 1)//&是位运算,b&1表示b在二进制下最后一位是不是1,如果是:
			ans *= base;//把ans乘上对应的a^(2^n)
		
        base *= base;//base自乘,由a^(2^n)变成a^(2^(n+1))
		b >>= 1;//位运算,b右移一位,如101变成10(把最右边的1移掉了),10010变成1001。现在b在二进制下最后一位是刚刚的倒数第二位。结合上面b & 1食用更佳
	}
	return ans;
}

字符串

字符串截取子串函数
一般调用格式:s.substr(start,len);
s.substr(2,5)为s从2位置开始的5个字符组成的子串。

字符串查找子串函数
一般调用格式:s.find(ss);
找到返回ss在s中的起始位置,否则返回-1。

字符串分割函数
一般调用格式:strtok(s,split);
分解字符串为一组字符串,s为要分解的字符串,split为分隔字符串。

字符串比较函数
一般调用格式:strcmp(str1,str2);
逐个字符比较两个字符串是否相等,相等则返回值为0,不相等返回第一次不同字符的ASCII码的差值。

字符串连接函数
一般调用格式:stract(str1,str2);
将str2指向的字符串连接到str1指向的字符串的后面。函数返回str1的值,即字符串str1的首地址。

字符串统计函数

一般调用格式:count(s.begin(),s.end(),'a');

背包

01背包

for (int i = 1; i <= n; i++)
    for (int j = V; j >= 0; j--)
        f[j] = max(f[j], f[j - c[i]] + w[i]);前面的代码中有f o r ( j = V . . . c [ i ] ),还可以将这个循环的下限进行改进。
假设当前物品为i ,即使在后面的循环中i + 1... n 的所有物体都要取,该下限也能保证最优解f [V] 能被更新到,该方法就是避免了这种情况,相当于舍弃了一些无用的状态,减少循环次数,但相应也降低了数组的完整性。f [ j − c [ i ] ]中的j 减去后面所有的c [ i ]才能达到这个bound,所以bound以下的状态是对答案没有贡献的。
for (int i = 1; i <= n; i++) {
    int bound = max(V - sum{c[i + 1]...c[n]}, c[i]);
    for (int j = V; j >= bound, j--)
        f[j] = max(f[j], f[j - c[i]] + w[i]);
}

初始化的细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求"恰好装满背包"时的最优解,有的题目则并没有要求必须把背包装满。这两种问法的区别是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了f [ 0 ]为0 其它f [ 1... V ]均设为− ∞,这样就可以保证最终得到的f [ N ]是一种恰好装满背包的最优解(其实f [ 1... j . . . n ] 分别对应着恰好装满容量为j的背包的最优解)。
如果并没有要求必须把背包装满,而是只希望所装物品价值尽量大,初始化时应该将f [ 0... V ] 全部设为0 (此时f [ j ]则表示不必装满容量为j 的背包的最优解)。
为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是− ∞ 了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0 了。这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。
其实根本区别在于转移。如果用物品根本无法凑出某个容量,设该容量为j ,则f [ j ] 永远不会通过f [ 0 ] = 0 得到正值,因为f数组的转移(变量j 的跳跃)是用V − c [ i ] 完成的,f [ j ] 只会一直在取max中获得负值(加了w [ i ]的负值)。


完全背包

for (int i = 1; i <= n; i++)
    for (int j = c[i]; j <= V; j++)
        f[j] = max(f[j], f[j - c[i]] + w[i]);

多重背包问题

for (int i = 1; i <= n; i++)
	for (int j = V; j >= c[i]; j--)
		for (int k = 1; k <= p[i] and k * c[i] <= j; k++)
			f[j] = max(f[j], f[j - c[i] * k] + w[i] * k);
for (int i = 1; i <= n; i++) {
    int num = min(p[i], V / c[i]); // V/c[i]是最多能放多少个进去,优化上界
    for (int k = 1; num > 0; k <<= 1) { //这里的k就相当于上面例子中的1,2,4,6
        if (k > num) k = num;
        num -= k;
        for (int j = V; j >= c[i] * k; j--) // 01背包
            f[j] = max(f[j], f[j - c[i] * k] + w[i] * k);
    }
}

分组背包问题

问题
有N 件物品和一个容量为V 的背包。第i 件物品的费用是c [ i ] ,价值是w [ i ] 。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

 

for (int i = 1; i <= n; i++) {
	cin >> s; // 第i组的物品数量
	for (int j = 1; j <= s; j++) cin >> c[j] >> w[j]; //组中每个物品的属性
    for (int j = V; j >= 0; j--)
        for (int k = 1; k <= s; k++)
            if (j >= c[k])
                f[j] = max(f[j], f[j - c[k]] + w[k]);
            // 由于每组物品只能选一个,所以可以覆盖之前组内物品最优解的来取最大值
}

记忆化搜索

线段树

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 7;

int n, m, s, t;
int ans;
int a[maxn];
struct segment_tree
{	
	struct node
	{
		int l, r;
		int num;
	}tr[maxn * 4];
	
	void build(int p, int l, int r)
	{
		tr[p] = {l, r, 0};
		if(l == r) {
			tr[p].num = a[l];
			return ;
		}
		int mid = l + r >> 1;
		build(p << 1, l, mid);
		build(p << 1 | 1, mid + 1, r);
	}		
	
	void modify(int p, int l, int r, int k) 
	{
		if(tr[p].l >= l && tr[p].r <= r) {
			tr[p].num += k;
			return ;
		}
		int mid = tr[p].l + tr[p].r >> 1;
		if(l <= mid) modify(p << 1, l, r, k);
		if(r > mid) modify(p << 1 | 1, l, r, k);
	}
	
	void query(int p, int x)
	{
		ans += tr[p].num;
		if(tr[p].l == tr[p].r) return;
		int mid = tr[p].l + tr[p].r >> 1;
		if(x <= mid) query(p << 1, x);
		else query(p << 1 | 1, x); 
	}
}ST;

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; ++ i) {
	    scanf("%d", &a[i]);
	}
	ST.build(1, 1, n);
    for (int i = 1; i <= m; ++ i) {
        int c;
        scanf("%d", &c);
        if(c == 1) {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            ST.modify(1, x, y, z);
        }
        else {
            ans = 0;
            int x;
            scanf("%d", &x);
            ST.query(1, x);
            printf("%d\n", ans);
        }
    }
	return 0;
}

int main()
{
	n = 100;
	for (int i = 1; i <= n; ++ i) {
		a[i] = i;
	}
	ST.build(1, 1, n);
	m = 10;
	for (int i = 1; i <= m; ++ i) {
		int l = 1, r = 100;
		ST.modify(1, l, r, 10000);
		ans = 0;
		// query(p, x), p = 1, x 为想要查询的点的下标
		ST.query(1, 50); // 单点查询 下标为 50 的点的值,ans = 50 + 10000 * i
		cout << i << " " << ans << '\n';
		ans = 0;
		ST.query(1, 100); // 单点查询 ans = 100 + 10000 * i
		cout << i << " " << ans << '\n'; 
	}
	return 0;
}

二分

模板1:

	while (l < r)
    {
        int mid = l + r >> 1;	//(l+r)/2
        if (check(mid))  r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }

模板2

	while (l < r)
    {
        int mid = l + r + 1 >> 1;	//(l+r+1)/2
        if (check(mid))  l = mid;
        else r = mid - 1;
    }

二分查找

#include<iostream>
using namespace std;

const int N=1000010;
int a[N],x,q,n;

int main(){
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	
	while(q--)
	{
		cin>>x;
		int l=1,r=n; //左右边界 
		while(l<r) //因为是找第一次出现的位置,那就是尽量往左来,就用模板1 
		{
			int mid=l+r>>1;
			if(a[mid]>=x) r=mid; //判断条件,如果值大于等于目标值,说明在目标值右边,尽量往左来
			else l=mid+1;
		}
		if(a[l]!=x){ //如果找不到这个值 
			cout<<-1<<" ";
			continue;
		}
		cout<<l<<" ";
	}
	return 0;
} 

什么是二分答案
答案属于一个区间,当这个区间很大时,暴力超时。但重要的是——这个区间是对题目中的某个量有单调性的,此时,我们就会二分答案。每一次二分会做一次判断,看是否对应的那个量达到了需要的大小。
判断:根据题意写个check函数,如果满足check,就放弃右半区间(或左半区间),如果不满足,就放弃左半区间(或右半区间)。一直往复,直至到最终的答案。

如何判断一个题是不是用二分答案做的呢?

1、答案在一个区间内(一般情况下,区间会很大,暴力超时)

2、直接搜索不好搜,但是容易判断一个答案可行不可行

3、该区间对题目具有单调性,即:在区间中的值越大或越小,题目中的某个量对应增加或减少。

lower_bound(),upper_bound()

跳石头

丢瓶盖

数列分段 Section II

树状数组

倍增

LCA

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<math.h>

#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)

using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=500007;
const ll INF=1e9+9;
const ll mod=2147483647;
const double EPS=1e-10;//-10次方约等于趋近为0
const double Pi=3.1415926535897;
ll n,m;
struct node
{
    ll u,v,nex;
}e[N<<1];
ll head[N],cnt;

void add(ll u,ll v)
{
    e[++cnt].v=v;
    e[cnt].u=u;//没什么用,还白占空间
    e[cnt].nex=head[u];
    head[u]=cnt;
}

ll depth[N],fa[N][30],lg[N],s,x,y;

/*dfs函数的作用就是更新该点的所有祖先的fa数组,并通过递归把
该节点的所有的子节点和该节点一样去更新*/
void dfs(ll now,ll fath)//子节点和父节点
{
    fa[now][0]=fath;//更新一下fa数组,2^0=1就是父节点
    depth[now]=depth[fath]+1;//更新深度
    over(i,1,lg[depth[now]]-1)
        fa[now][i]=fa[fa[now][i-1]][i-1];
        /*更新now的所有 2^i 级的祖先。先找到now的2^(i-1)级祖先,再往上找
        该祖先的2^(i-1)级祖先,就是now的2^i祖先,必须一节一节地往上搜*/
    for(ll i=head[now];i;i=e[i].nex)//链式前向星遍历
        //如果now有子节点的话,就递归往子节点的子节点走(禁止套娃)
        if(e[i].v!=fath)
            dfs(e[i].v,now);
}

inline ll LCA(ll x,ll y)
{
    if(depth[x]<depth[y])//用数学语言就是说不妨设x的深度比y的深度大
        swap(x,y);//这样下面只需要写一种代码就好了
    while(depth[x]>depth[y])
        //让x跳到y的高度(同一高度)
        x=fa[x][lg[depth[x]-depth[y]]-1];
    //如果跳到一块了那LCA肯定就是y了
    if(x==y)
        return x;
    for(ll k=lg[depth[x]]-1;k>=0;--k)//倒着从大到小地跳
        /*因为我们要求跳到x和y的LCA的下一层,所以没有跳到的时候就
        让x和y利用dfs里早就用倍增算法处理过的祖先路径快速地一块往上跳*/
        if(fa[x][k]!=fa[y][k])
            x=fa[x][k],y=fa[y][k];//往上跳
    return fa[x][0];//返回x,y的父节点(肯定是相同的嘛)
}

int main()
{
    scanf("%lld%lld%lld",&n,&m,&s);
    over(i,1,n-1)
    {
        scanf("%lld%lld",&x,&y);
        add(x,y),add(y,x);//无向图一定要记得建双向边
    }
    over(i,1,n)//预处理一下
    lg[i]=lg[i-1]+(1<<lg[i-1]==i);//log2(8)=3//这个手写的lg[]要-1才能用lg[8]=4;
    dfs(s,0);//从树根开始,因为用的是链式前向星所以给一个假想根0(其实就是到这儿停)
    //dfs一下,预处理各点的深度和祖先
    over(i,1,m)
    {
        scanf("%lld%lld",&x,&y);
        printf("%lld\n",LCA(x,y));
    }
    return 0;
}

欧几里得

int gcd(int a, int b){
    if(b == 0)
        return a;
    else
        return gcd(b, a % b);
}

快读快写

int read() {
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
void write(int x) {
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

time

stop = clock();
double k = (double)(stop - start) / CLOCKS_PER_SEC;
std::cout << "time: "<< k << "s" <<std::endl;

rand

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

int main(){
    srand(time(0);
    int k = rand() % 2;
    cout << k << endl;
    return 0;
}

对拍

OI搜

信息学竞赛常用函数/模板

数据结构和算法动态可视化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值