Loading

各种模板

并查集

int merge(int root) //查找根节点
{
    ///循环写法
    int rt, tmp;
    rt = root;
    while(root != pre[root])
        root = pre[root];
    while(rt != root)
    {
        tmp = pre[rt];
        pre[rt] = root;
        rt = tmp;
    }
    return root;
    ///递归
    if(root == pre[root])
        return root;
    return pre[root] = merge(pre[root]);
}
void join(int rt1, int rt2) //判断是否连通,不连通就合并
{
    int x, y;
    x = merge(rt1);
    y = merge(rt2);
    if(x != y)
        pre[x] = y;
}

杨辉三角求组合数

void fun(){
    c[0][0] = 1;
    for(int i = 1;i <= 12;i++){
        for(int j = 0;j <= i;j++){
            if(j == 0)
                c[i][j] = 1;
            else
                c[i][j] = c[i-1][j] + c[i-1][j-1];
        }
    }
}

__int128的读写

inline __int128 read()
{
    static char c=nc();
    int x=0,f=1;
    for(;c>'9'||c<'0';c=nc())
        if(c=='-') f=-1;
    for(;c<='9'&&c>='0';c=nc())
        x=(x<<3)+(x<<1)+c-48;
    return x*f;
}
inline void write(__int128 x)
{
   if(x<0){putchar('-');x=-x;}
   if(x>9) write(x/10);
   putchar(x%10+'0');
}//需要在主函数补上换行

高精度算法

基于vector实现

vector<int> Max(vector<int> x, vector<int> y) {
    if (x.size() > y.size())return x;
    if (x.size() < y.size())return y;
    if (vector<int>(x.rbegin(), x.rend()) > vector<int>(y.rbegin(), y.rend()))return x;
    return y;
}

输出,逆序

for(int i = res.size()-1;i >= 0;i--)
    cout << res[i];
cout << endl;

加法

vector<int> add(vector<int> x,vector<int> y)
{
    vector<int> res;
    int t = 0;
    for(int i = 0;i < x.size()||i < y.size();i++)
    {
        if(i < x.size()) t += x[i];
        if(i < y.size()) t += y[i];
        res.push_back(t%10);
        t /= 10;
    }
    if(t) res.push_back(1);
    return res;
}

减法

bool cmp(vector<int> a, vector<int> b) {
    //比较位数,位数大的数值肯定大。
    if (a.size() != b.size()) return a.size() > b.size();

    //位数相同,从高位往低位比较每个位置的数值。
    for (int i = a.size() - 1;i >= 0;i--)
        if (a[i] != b[i]) return a[i] > b[i];
    
    return true;
}
void sub(vector<int> &a, vector<int> &b,vector<int> &c) {
    for (int i = 0, t = 0;i < a.size();i++) {
        //a是大的数字,所以以a的位数作为结束
        t += a[i];
        //看看是否需要-1,前方的数值有木有借位。
        if (i < b.size()) t -= b[i];
        //看看是否还有b,有的话就相减
        //没有的话直接存入位数中。
        c.push_back((t + 10) % 10);
        //加10模10防止a[i]小了,不够,减成了负号
        if (t < 0) t = -1;
        //t带了负号,说明借了位,变为-1记录借了位。
        else t = 0;
        //不是负号,初始化为0,什么事也没发生。
    }
    while (c.size() > 1 && !c.back()) c.pop_back();
    //去掉前置零。
}
/*留意
    if (cmp(a, b)) sub(a, b, c);
    //减不过,就只能添负号,让b-a。
    else sub(b, a, c), cout << "-";
*/

乘法(高精度*整数低精度)

vector<int> mul(vector<int> x, int y) {
    vector<int> res;
    int t = 0;
    for (int i : x) {
        t += i * y;
        res.push_back(t % 10);
        t /= 10;
    }
    while (t) {
        res.push_back(t % 10);
        t /= 10;
    }
    /*处理前置零
    while (res.size() > 1 && !res.back()) res.pop_back();*/
    return res;
}
/// 大数相乘
vector<int> mul(vector<int> x,vector<int> y)
{
    vector<int> res;
    res.assign(a.size() + b.size(), 0);
    int t;
    for(int i = 0;i < x.size();i++)
    {
        t = i;
        for(int j = 0;j < y.size();j++){
            res[t++] += a[i] * b[j];
        }
    }
    for(int i = 0;i < res.size();i++){
        if(res[i] > 9){
            res[i+1] += res[i] / 10;
            res[i] %= 10;
        }
    }
    while (res.size() > 1 && !res.back())
        res.pop_back();
    return res;
}

除法

vector<int> div(vector<int> x, int y) {
    vector<int> res;
    int t = 0;
    bool is_first = true;
    for (int i = x.size() - 1; i >= 0; i--) {
        t = t * 10 + x[i];
        int tc = t / y;
        if (!is_first || tc) {
            is_first = false;
            res.push_back(tc);
            t %= y;
        }
    }
    reverse(res.begin(), res.end());
    /*处理前置零
    while (c.size() > 1 && !c.back()) c.pop_back();*/
    return res;
}

运算符重载

struct node{
    int l, r, x;
    node(int L, int R, int X) {
        l = L, r = R;
        x = X;
    }
    bool operator < (const node &rhs) const{
        if(x != rhs.x) return x < rhs.x;
        return l > rhs.l;
    }
};

常见的素数求法

1.常规循环做法

bool judge(int n)
{
    if(n < 2)
        return false;
    for(int i = 2;i <= sqrt(n);i++){
        if(n % i == 0)
            return false;
    return true;
    }
}

2.埃式筛法

复杂度:O(nloglogn)

介绍:埃拉托斯特尼筛法,利用当前已经找到的素数,从后面的数中筛去当前素数的倍数由预备知识一可知,当前素数已经是筛去数的质因子,如此下去能筛除所有之后的合数,是一种比较快的筛法

int prime[MAXN];
bool isprime[MAXN];
int calcprime(int n)
{
    memset(isprime,true,sizeof(isprime)); //初始化
    int p = 0;
    isprime[0] = isprime[1] = false;
    for(int i = 2;i <= n;i++){
        if(isprime[i]){
            prime[p++] = i;
            for(int j = 2*i;j <= n;j += i)  ///缺点,会重复筛比如8和16
                isprime[j] = false;
        }
    }
    return p;   ///素数的个数
}

3.欧几里得筛法

复杂度:O(N)

介绍:这是一种很好的线性筛法和埃氏筛法的区别是对于每一个要筛除的数,欧拉筛法只筛除一次而埃氏筛法会重复筛除,比如8和16同时被2和4筛去,推荐使用欧拉筛法

int prime[MAXN];
int factor[MAXN];   ///用来记录最小素因子
bool isprime[MAXN];
int calcprime(int n)
{
    int p = 0;
    for(int i = 2;i <= n;i++){
        if(!factor[i]){  ///如果没有找到素因子,当然就是素数
            prime[p++] = i;
            factor[i] = i;
        }
        for(int j = 0;j < p&&prime[j]*i <= n;j++){
            factor[prime[j]*i] = prime[j];
            if(!(i % prime[j]))
                break;
        }
    }
    return p;
}

排序

快速排序

void quicksort(int arr[], int l, int r)		//0~(n-1),传参为quicksort(arr, 0, n-1)
{
    if(l < r){
        int tmp = arr[l];
        int i = l, j = r;
        while(i < j){
            while(i < j && arr[j] >= tmp)
                j--;
            arr[i] = arr[j];
            while(i < j && arr[i] <= tmp)
                i++;
            arr[j] = arr[i];
        }
        arr[i] = tmp;
        quicksort(arr, l, i-1);
        quicksort(arr, i+1, r);
    }
}

归并排序

int a[M], b[M];
void merge(int l, int mid, int r)
{
    int i = l, j = mid + 1;
    int k = l;
    while(i <= mid&&j <= r){
        if(a[i] <= a[j])
            b[k++] = a[i++];
       	else
            b[k++] = a[j++];
    }
    while(i <= mid)
        b[k++] = a[i++];
   	while(j <= r)
       	b[k++] = a[j++];
    for(int s = 0;s < k;s++)
        a[s] = b[s];
}

void mergesort(int l, int r)	//0~n的数组 mergesort(0, n-1);
{
    if(l == r)
        return ;
   	else{
        int mid = (l + r) >> 1;
        mergesort(l, mid);
        mergesort(mid + 1, r);
        merge(l, mid , r);
    }
}

图论

线段树(待更新)

struct node{
	int l, r;
	int sum;
}tree[MAXN<<2];
int num[MAXN];
void build(int l, int r, int i)
{
	tree[i].l = l;
	tree[i].r = r;
	if(l == r)
	{
		tree[i].sum = num[i];
		return ;
	}
	int mid = (l + r) >> 1;
	build(l,mid,i<<1);
	build(mid+1,r,i<<1|1);
	tree[i].sum = tree[i<<1].sum + tree[i<<1|1].sum;
}

树状数组

int a[],c[]; /// c树状数组, a原数组
int lowbit(x)
{
    return x & (-x);
}
/// 单点修改
void update(int x, int y, int num)
{
    for(int i = x;i <= y;i += lowbit(x))
        c[i] += num;
}
/// 区间查询
int getsum(int x)
{
    int ans = 0;
    for(int i = x;i > 0;i -= lowbit(x))
        ans += c[i];
    return ans;
}
/// 求逆序对
void update(int p) /// 离散化
{
    while(p <= n)
    {
        a[p]++;
        p += lowbit(p);
    }
}
int getsum(int p)
{
    int res = 0;
    while(p)
        res += a[p],p -= lowbit(p);
    return res;
}
int getans()
{
    for(int i=1;i<=n;i++)
    {
        update(b[i]+1);
        res += i-getsum(b[i]+1);
    }
}
/// 求区间最大值
void Update(int i,int v)
{
    while(i<=maxY)
    {
        t[i] = max(t[i],v);
        i += lowbit(i);
    }
}
int query(int i)
{
    int ans = 0;
    while(i)
    {
        ans = max(ans,t[i]);
        i -= lowbit(i);
    }
    return ans;
}
/// 区间修改+单点查询
void add(int p, int x){ //这个函数用来在树状数组中直接修改
    while(p <= n) sum[p] += x, p += p & -p;
}
void range_add(int l, int r, int x){ //给区间[l, r]加上x
    add(l, x), add(r + 1, -x);
}
int ask(int p){ //单点查询
    int res = 0;
    while(p) res += sum[p], p -= lowbit(p);
    return res;
}
/// 区间修改+区间查询
void add(ll p, ll x){
    for(int i = p; i <= n; i += lowbit(i))
        sum1[i] += x, sum2[i] += x * p;
}
void range_add(ll l, ll r, ll x){
    add(l, x), add(r + 1, -x);
}
ll ask(ll p){
    ll res = 0;
    for(int i = p; i; i -= lowbit(i))
        res += (p + 1) * sum1[i] - sum2[i];
    return res;
}
ll range_ask(ll l, ll r){
    return ask(r) - ask(l - 1);
}

最短路

Floyd

时间复杂度为o(n^3)
使用范围:无负权回路即可,边权可正可负,运行一次算法即可求得任意两点间最短路

void Floyd()
{
    for(int i = 1;i <= n;i++)
        dp[i][i] = 0;
    for(int k = 1;k <= n;k++)
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= n;j++)
                dp[i][j] = min(dp[i][j], dp[i][k]+dp[k][j]);
}

Dijkstra

时间复杂度o(n^2)(优化前)

使用范围:无负权回路,边权必须非负,单源最短路

int dijkstra(int s, int t)///s到t的距离
{
    memset(vis, false, sizeof(vis));
    for (int i = 1; i <= n; ++i) ///初始化各点到s点的距离
    {
        dis[i] = map_dis[s][i];
    }
    dis[s] = 0, vis[s] = true;

    for (int i = 0; i < n - 1; ++i){ ///除s点外找n-1个点  
        int u, tmin = inf;
        for (int j = 1; j <= n; ++j){ ///找min{dis[]}   
            if(!vis[j] && dis[j] < tmin)
            {
                tmin = dis[j];
                u = j;
            }
        }
        /// if(tmin == inf) return -1;   //无最短路
        vis[u] = true;                  ///进入T集合
        for (int v = 1; v <= n; ++v){    ///更新相邻点  
            if(!vis[v] && dis[u] + map_dis[u][v] < dis[v]){
                dis[v] = dis[u] + map_dis[u][v];
            }
        }
    }
    return dis[t];
}

SPFA

时间复杂度为o(KE)

使用范围:边权可正可负,单源最短路,还可以判断图中有无负权回路
原理:将源点加入队列,然后不断从队列中弹出顶点u,遍历u的邻接点v进行松弛更新(若dis[v] < dis[u] + distance[u][v] 则更新dis[v]为dis[u] + distance[u]),更新后如果v点不在队列里则进入队列。

struct Edge{
    int v, w, next;
} edge[maxm * 2];
void add_edge(int u, int v, int w)  ///邻接表前插法
{
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}
int spfa(int s, int t)
{
    queue<int>q;
    q.push(s);
    dis[s] = 0;
    inq[s] = 1;
    while (!q.empty())
    {
        int u = q.front(); q.pop();
        inq[u] = 0;
        for (int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].v;
            int w = edge[i].w;
            if (dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                if (!inq[v])
                {
                    inq[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    return dis[t];
}

队列式判断负权环

bool spfa(int s, int t){
    queue<int>q;
    q.push(s);
    dis[s] = 0;
    inq[s] = 1;
    times[s]++;
    while (!q.empty()){
        int u = q.front(); q.pop();
        inq[u] = 0;
        for (int i = head[u]; i != -1; i = edge[i].next) {
            int v = edge[i].v;
            int w = edge[i].w;
            if (dis[v] > dis[u] + w){
                dis[v] = dis[u] + w;
                if (!inq[v]){
                    inq[v] = 1;
                    q.push(v);
                    times[v]++;
                    if(times[v] > n){
                        return false;
                    }
                }
            }
        }
    }
    return true;
}

Bellman-Ford

时间复杂度:O(VE)

适用范围:边权可正可负,单源最短路,还可以判断图中有无负权回路

void Bellman_Ford()
{
    for(int k=1; k<=n-1; k++)
    {
        flag1=0;///用于标记本轮松弛是否发生
        for(int i=1; i<=m; i++)
        {
            if(dis[v[i]]>dis[u[i]]+w[i])
                dis[v[i]]=dis[u[i]]+w[i];
            flag1=1;//有更新
        }
        if(flag1==0)
            break;///dis数组没有更新,即没有松弛,结束算法
    }
    flag2=0;
    for(int i=1; i<=m; i++)
        if(dis[v[i]]>dis[u[i]]+w[i])
            flag2=1;
    if(flag2==1)
        printf("有负权回路\n");
    else
    {
        for(int i=1; i<=n; i++)
            printf("%d ",dis[i]);
    }
}

最小生成树

Kruskal算法

思想:先从小到大排序各条边的长度,再依次搜索已排序好的边。搜查到的边的两端点如果不在一个集合里,就合并两个集合亦即是两端点没在同一个连通图中,即合并两个点所在的图; 直到所有点在同一个集合(同一张图),即可。因为每一次选取的都是能够和存放最小生成树点的集合中的点相连的最小的边,所以最后得到的边权总和最小。

///使用了并查集的思想
struct edge
{
    //u v 为边的前后节点
    int u;
    int v;
    int w;//权重
} e[MAXN];
bool cmp(const int a,const int b)
{
    //间接比较,实现对边权大小的排序
    return e[a].w<e[b].w;
}
int find(int x)
{
    int t = x;
    while(t!=pre[t])
        t=pre[t];  //找到根结点
    int i=x,j;
    //优化步骤,让所有子节点都直接与父节点相连,使每次搜索父节点的效率提高很多
    if(pre[i]!=t)//
        while(t!=i)//压缩路径,将树的深度变成只有 2,即所有叶都直接连在同一根上
        {
            j=pre[i];
            pre[i]=t;
            i=j;
        }
    return t;
}
int Kruscal()
{
    int ans=0;
    for(int i=1;i<=n;++i)
        pre[i] = i; //每个点的父节点一开始都是它本身,因为此时每个点占了一个集合 
    for(int i=1;i<=m;++i) // r[]数组存放的是边的序号 
        sort(r,r+m,cmp); //对边的权值从小到大进行排序 
    for(int i=1; i<=m; ++i)
    {
        int x=find(e[r[i]].u);
        int y=find(e[r[i]].v);
        if(x!=y)//两个点不在同一个集合,则合并
        {
            //根节点相连
            pre[x]=y;  //将其中一个设置为另外一个的父节点,则连接了两个集合
            ans+=e[r[i]].w;
        }
    }
    return ans;  //最小总权值
}

Prim算法

思想:在带权连通图中V是包含所有顶点的集合,U已经在最小生成树中的节点的集合.从图中任意某一顶点v开始,此时集合U={v}重复执行下述操作:在所有u∈U,v∈V-U的边(u,v)∈E中找到一条权值最小的边,将(u,w)这条边加入到已找到边的集合,并且将点w加入到集合U中,当U=V时,就找到了这颗最小生成树。

任意选择一个起点 u,每次把点添加到最小生成树点的集合U中,都把添加进去的点跟集合内其他点的距离设置为0,表示在集合中

int Prim(int u)
{
	memset(vis,0,sizeof(vis));
    for (int i = 1; i <= n; i++)
        dis[i] = (i==u?0:g[u][i]); //dis[i] 表示是的 节点 i 到集合U的最短距离
	vis[1] = 1;
//N-1次遍历,每次都从集合N中找到一个结点,距离集合Y中所有结点值最小
    for (int i = 1; i < n; i++)
    {
        int temp=INF,k;
        for (int j = 1; j <= n; j++)
            //找一条到集合U的最小的边
            if(!vis[j]&&dis[j]<temp )
            {
                temp = dis[j];
                k = j;
            }
        ans+=temp;
        vis[k] = 1;//将节点加入到 U(最小生成树点集)中
        //更新N集合中剩余的其他结点,因为dis[k]发生了变化
        for (int j=1; j <= n; j++)
            if(!vis[j]&&dis[j] > g[j][k])
                dis[j] = g[j][k];
    }
    return ans;//最小总权值
}

数论

辗转相除法(欧几里得求最大公约数)

int gcd(int a, int b)
{
    return a % b == 0?b:gcd(b,a%b);
}

快速幂取模,时间复杂度o(logn)

ll qpow(ll a, ll b, ll MOD) ///求a^b%MOD
{
	ll res = 1;
	while(b)
	{
		if(b & 1)
			res = res * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return res;
}

逆元(a / b % p = a * b的逆 % p)求法qpow(b, p - 2, p);

龟速乘常搭配快速幂一起使用,防止快速幂爆范围

ll mul(ll a, ll b, ll p)
{
    ll ans = 0;
    while(b){
        if(b & 1)
            ans = (ans + a) % p;
        a = a * 2 % p;
        b >>= 1;
    }
    return ans;
}

欧拉函数

暴力求法

优化版时间复杂度为\(o(\sqrt{n}))\)

ll eular(int n)
{
	ll res = n;
	for(int i = 2;i*i <= n;i++)
		if(n % i == 0)
		{
			res = res / i * (i-1);
			while(n%i == 0)
				n /= i;
		}
	if(n > 1)
		res = res / n * (n-1);
	return res;
}

递推式求欧拉函数

void eular()
{
	for(i=1; i<=maxn; i++)
		p[i]=i;
	for(i=2; i<=maxn; i+=2)
		p[i]/=2;
	for(i=3; i<=maxn; i+=2)
		if(p[i]==i)
		{
			for(j=i; j<=maxn; j+=i)
				p[j]=p[j]/i*(i-1);
		}
}

埃拉托斯特尼筛求欧拉函数

void euler(int n)
{
    for (int i=1;i<=n;i++) phi[i]=i;
    for (int i=2;i<=n;i++)
    {
        if (phi[i]==i)//这代表i是质数
        {
            for (int j=i;j<=n;j+=i)
            {
                phi[j]=phi[j]/i*(i-1);//把i的倍数更新掉
            }
        }
    }
}

欧拉筛求欧拉函数

void euler(int n)
{
	phi[1]=1;//1要特判 
	for (int i=2;i<=n;i++)
	{
		if (flag[i]==0)//这代表i是质数 
		{
			prime[++num]=i;
			phi[i]=i-1;
		}
		for (int j=1;j<=num&&prime[j]*i<=n;j++)//经典的欧拉筛写法 
		{
			flag[i*prime[j]]=1;//先把这个合数标记掉 
			if (i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];//若prime[j]是i的质因子,则根据计算公式,i已经包括i*prime[j]的所有质因子 
				break;//经典欧拉筛的核心语句,这样能保证每个数只会被自己最小的因子筛掉一次 
			}
			else phi[i*prime[j]]=phi[i]*phi[prime[j]];//利用了欧拉函数是个积性函数的性质 
		}
	}
}

字符串匹配

KMP

A数组代表原字符串,B数组代表匹配子串

void solve()
{
	int n = strlen(A); ///原字符串长度
	int m = strlen(B); ///子串长度
	int F[m+1]; ///代表B子串的最长前缀
	F[0] = -1; /// 用-1便于判断是否越界
	for(int i = 1;i < m;i++)   ///求解F数组
	{
		int j = F[i-1];
		while((B[j+1] != B[i])&& j >= 0)
			j = F[j];
		if(B[j+1] == B[i])
			F[i] = j + 1;
		else
			F[i] = -1;
	}
	int i = 0, j = 0;
	while(i < n)
	{
		if(A[i] == B[j])
		{
			i++;
			j++;
			if(j == m) /// 成功完成一次匹配
			{
				///内容取决题目要求
				j = F[j-1] + 1;
			}
		}
		else
		{
			if(j) ///不匹配的位置在有相同前缀时,直接回到该位置
				j = F[j-1] + 1;
			else ///不匹配的位置没有相同前缀时,往后移动到出现相同的位置
				i++;
		}
	}
}
posted @ 2020-10-24 16:28  哈啤丶  阅读(79)  评论(0)    收藏  举报