学习汇总

并查集

首先在地图上给你若干个城镇,这些城镇都可以看作点,然后告诉你哪些对城镇之间是有道路直接相连的。最后要解决的是整幅图的连通性问题。比如随意给你两个点,让你判断它们是否连通,或者问你整幅图一共有几个连通分支,也就是被分成了几个互相独立的块。像畅通工程这题,问还需要修几条路,实质就是求有几个连通分支。

如果是1个连通分支,说明整幅图上的点都连起来了,不用再修路了;如果是2个连通分支,则只要再修1条路,从两个分支中各选一个点,把它们连起来,那么所有的点都是连起来的了;如果是3个连通分支,则只要再修两条路……
以下面这组数据输入数据来说明
4 2 1 3 4 3
第一行告诉你,一共有4个点,2 条路。下面两行告诉你,1、3之间有条路,4、3之间有条路。那么整幅图就被分成了1-3-4和2两部分。只要再加一条路,把2和其他任意一个点连起来,畅通工程就实现了,那么这个这组数据的输出结果就是1。好了,现在编程实现这个功能吧,城镇有几百个,路有不知道多少条,而且可能有回路。 这可如何是好?

####并查集由一个整数型的数组和两个函数构成。数组pre[]记录了每个点的前导点是什么,函数find是查找,join是合并。
####对于集合里的任意两个元素x,y而言,它们之间必定存在着某种联系,因为并查集中的元素均是有联系的(这点是并查集的实质,要深刻理解),否则也不会被合并到当前集合中

并查集的基本操作有三个:

1、makeSet(s):建立一个新的并查集,其中包含 s 个单元素集合。
2、join(x, y):把元素 x 和元素 y 所在的集合合并,要求 x 和 y 所在的集合不相交,如果相交则不合并。
3、find(x):找到元素 x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。

const int MAXSIZE = 500;
int pre[MAXSIZE]; //存放第i个元素的父节点
 
void makeSet(int size) {
    for(int i = 1;i <= size;i++) pre[i] = i;
}
这个是非递归版的find函数

int find(int root) //查找根结点
{
	int son, tmp;
	son = root;
	while(root != pre[root]) //寻找根结点
		root = pre[root];
	while(son != root) //路径压缩
	{
		tmp = pre[son];
		pre[son] = root;
		son = tmp;
	}
	return root;
}
这个是递归版的find函数

int find(int x) {
    if (x != uset[x]) uset[x] = find(uset[x]);
    return uset[x];
}
 
void join(int root1, int root2) //判断是否连通,不连通就合并
{
	int x, y;
	x = find(root1);
	y = find(root2);
	if(x != y) //如果不连通,就把它们所在的连通分支合并
		pre[x] = y;
}

完整代码如下:


#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int pre[1010]; //里面全是掌门
 
int find(int root)
{
	int son, tmp;
	son = root;
	while(root != pre[root]) //寻找掌门ing……
		root = pre[root];
	while(son != root) //路径压缩
	{
		tmp = pre[son];
		pre[son] = root;
		son = tmp;
	}
	return root; //掌门驾到~
}
 
int main()
{
	int num, road, total, i, start, end, root1, root2;
	while(scanf("%d%d", &num, &road) && num)
	{
		total = num - 1; //共num-1个门派
		for(i = 1; i <= num; ++i) //每条路都是掌门
			pre[i] = i;
		while(road--)
		{
			scanf("%d%d", &start, &end); //他俩要结拜
            //这一段就是join函数
			root1 = find(start);
			root2 = find(end);
			if(root1 != root2) //掌门不同?踢馆!~
			{
				pre[root1] = root2;
				total--; //门派少一个,敌人(要建的路)就少一个
			}
		}
		printf("%d\n", total);//天下局势:还剩几个门派
	}
	return 0;
}

二分查找

二分查找就是将查找的键和子数组的中间键作比较,如果被查找的键小于中间键,就在左子数组继续查找;如果大于中间键,就在右子数组中查找,否则中间键就是要找的元素。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0vUiSaNN-1577329535156)(https://images2015.cnblogs.com/blog/772134/201608/772134-20160813105218234-296133868.png “二分查找”)]

/**
 * 二分查找,找到该值在数组中的下标,否则为-1
 */
static int binarySerach(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] == key) {
            return mid;
        }
        else if (array[mid] < key) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }

    return -1;
}

每次移动left和right指针的时候,需要在mid的基础上+1或者-1, 防止出现死循环, 程序也就能够正确的运行。

注意:代码中的判断条件必须是while (left <= right),否则的话判断条件不完整,比如:array[3] = {1, 3, 5};待查找的键为5,此时在(low < high)条件下就会找不到,因为low和high相等时,指向元素5,但是此时条件不成立,没有进入while()中。

二分查找的变种

关于二分查找,如果条件稍微变换一下,比如:数组之中的数据可能可以重复,要求返回匹配的数据的最小(或最大)的下标;更近一步, 需要找出数组中第一个大于key的元素(也就是最小的大于key的元素的)下标等等。 这些,虽然只有一点点的变化,实现的时候确实要更加的细心。

二分查找的变种和二分查找原理一样,主要就是变换判断条件(也就是边界条件),如果想直接看如何记忆这些变种的窍门,请直接翻到本文最后。下面来看几种二分查找变种的代码:

1 查找第一个与key相等的元素

查找第一个相等的元素,也就是说等于查找key值的元素有好多个,返回这些元素最左边的元素下标。

// 查找第一个相等的元素
static int findFirstEqual(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] >= key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    if (left < array.length && array[left] == key) {
        return left;
    }
    
    return -1;
}

2 查找最后一个与key相等的元素

查找最后一个相等的元素,也就是说等于查找key值的元素有好多个,返回这些元素最右边的元素下标。

// 查找最后一个相等的元素
static int findLastEqual(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] <= key) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }
    if (right >= 0 && array[right] == key) {
        return right;
    }

    return -1;
}

3 查找最后一个等于或者小于key的元素

查找最后一个等于或者小于key的元素,也就是说等于查找key值的元素有好多个,返回这些元素最右边的元素下标;如果没有等于key值的元素,则返回小于key的最右边元素下标。

// 查找最后一个等于或者小于key的元素
static int findLastEqualSmaller(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] > key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return right;
}

4 查找最后一个小于key的元素

查找最后一个小于key的元素,也就是说返回小于key的最右边元素下标。

// 查找最后一个小于key的元素
static int findLastSmaller(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] >= key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return right;
}

5 查找第一个等于或者大于key的元素

查找第一个等于或者大于key的元素,也就是说等于查找key值的元素有好多个,返回这些元素最左边的元素下标;如果没有等于key值的元素,则返回大于key的最左边元素下标。

// 查找第一个等于或者大于key的元素
static int findFirstEqualLarger(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] >= key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return left;
}

6 查找第一个大于key的元素

查找第一个等于key的元素,也就是说返回大于key的最左边元素下标。

// 查找第一个大于key的元素
static int findFirstLarger(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 这里必须是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] > key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return left;
}

二分查找变种总结

left=0;
right=array.length-1;
// 这里必须是 <=
while (left <= right) {
    int mid = (left + right) / 2;
    if (array[mid] ? key) {
        //... right = mid - 1;
    }
    else {
        // ... left = mid + 1;
    }
}
return xxx;

二分查找变种较多,不过它们的“套路”是一样的,以上代码就是其套路,如何快速写出二分查找的代码,只需按照以下步骤即可:

1 首先判断出是返回left,还是返回right

因为我们知道最后跳出while (left <= right)循环条件是right < left,且right = left - 1。最后right和left一定是卡在"边界值"的左右两边,如果是比较值为key,查找小于等于(或者是小于)key的元素,则边界值就是等于key的所有元素的最左边那个,其实应该返回left。

以数组{1, 2, 3, 3, 4, 5}为例,如果需要查找第一个等于或者小于3的元素下标,我们比较的key值是3,则最后left和right需要满足以下条件:
alt
我们比较的key值是3,所以此时我们需要返回left。

2 判断出比较符号

int mid = (left + right) / 2;
if (array[mid] ? key) {
    //... right =mid-1;
}
else {
    // ... left = mid+1;
}

也就是这里的 if (array[mid] ? key) 中的判断符号,结合步骤1和给出的条件,如果是查找小于等于key的元素,则知道应该使用判断符号>=,因为是要返回left,所以如果array[mid]等于或者大于key,就应该使用>=

单调栈

假设有一个单调递增的栈 S和一组数列:
a : 5 3 7 4

用数组L[i] 表示 第i个数向左遍历的第一个比它小的元素的位置

如何求L[i]?

首先我们考虑一个朴素的算法,可以按顺序枚举每一个数,然后再依此向左遍历。
但是当数列单调递减时,复杂度是严格的O(n^2)。

此时我们便可以利用单调栈在O(n)的复杂度下实现

我们按顺序遍历数组,然后构造一个单调递增栈

(1). i = 1时,因栈为空,L[1] = 0,此时再将第一个元素的位置下标1存入栈中

此时栈中情况:

(2).i = 2时,因当前3小于栈顶元素对应的元素5,故将5弹出栈
此时栈为空
故L[2] = 0
然后将元素3对应的位置下标2存入栈中

此时栈中情况:

(3).i = 3时,因当前7大于栈顶元素对应的元素3,故
L[3] = S.top() = 2 (栈顶元素的值)

然后将元素7对应的下标3存入栈
此时栈中情况:

(4).i = 4时,为保持单调递增的性质,应将栈顶元素3弹出
此时 L[4] = S.top() = 2;

然后将元素4对应的下标3存入栈
此时栈中情况:

至此 算法结束
对应的结果:
a : 5 3 7 4
L : 0 0 2 2

总结:一个元素向左遍历的第一个比它小的数的位置就是将它插入单调栈时栈顶元素的值,若栈为空,则说明不存在这么一个数。然后将此元素的下标存入栈,就能类似迭代般地求解后面的元素

for(int i = 1; i <= n; i++)
	    {
	    	while(st.size() && a[i] < a[st.top()]) 
			    st.pop();
	    	l[i] = st.empty()? 1 : st.top()+1;
	    	st.push(i);
		}//L【i】是向左走第一个比他小的
while(st.size())st.pop();//每用完一次记得清空整个栈
for(int i = n; i >= 1; i--)
		{
			while(st.size() && a[i] <= a[st.top()])
			     st.pop();
			r[i] = st.empty()? n : st.top()-1;
			st.push(i);
		}//R【i】是向右走第一个比他小的
while(st.size())st.pop();
	    for(int i = 1; i <= n; i++)
	    {
	    	while(st.size() && a[i] >= a[st.top()])
			    st.pop();
	    	l[i] = st.empty()? 1 : st.top()+1;
	    	st.push(i);
		}//L【i】是向左走第一个比他大的
while(st.size())st.pop();
		for(int i = n; i >= 1; i--)
		{
			while(st.size() && a[i] > a[st.top()])
			     st.pop();
			r[i] = st.empty()? n : st.top()-1;
			st.push(i);
		}//R【i】是向右走第一个比他大的

高精度

本质就是模拟竖式运算

int lens=1,lenm=1,lena=1;
int sum[10010] = {0, 1}, maxn[10010] = {0, 1}, ans[10010];

高精度乘法

void muti(long long x)
{
    int tmp = 0;
    for(int i = 1; i <= lens; i++)
        sum[i] *= x;
    for(int i = 1; i <= lens; i++)
    {
        tmp += sum[i];
        sum[i] = tmp %10;
        tmp /= 10;
    }
    while(tmp != 0)
    {
        lens++;
        sum[lens] = tmp % 10;
        tmp /= 10;
    }
}

高精度除法

void cut(long long x)
{
    memset(ans, 0, sizeof(ans));
    lena = lens;
    int tmp = 0;
    for(int i = lena; i >= 1; i--)
    {
        tmp *= 10;
        tmp += sum[i];
        if(tmp >= x)
        {
            ans[i] = tmp / x;
            tmp %= x;
        }
    }
    while(ans[lena] == 0)
    {
        if(lena == 1)
            break;
        lena--;
    }
}

结果更新

void max()
{
    if(lena > lenm)
    {
        for(int i = 1; i <= lena; i++)
            maxn[i] = ans[i];
        lenm = lena;
    }
    else if(lena == lenm)
    {
        for(int i = lena; i >= 1; i--)
            if(maxn[i] < ans[i])
            {
                for(int j = 1; j <= lena; j++)
                    maxn[j] = ans[j];
                lenm = lena;
                break;
            }
    }
}

快速幂

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

高精度快速幂

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

int base[1001],ans[1001],save[1001];
int p;

void mult()
{
    memset(save,0,sizeof(save));
    for(int i=1;i<=500;i++)
        for(int j=1;j<=500;j++)
            save[i+j-1]+=ans[i]*base[j];
    for(int i=1;i<=500;i++)
    {
        save[i+1]+=save[i]/10;
        save[i]=save[i]%10;
    }
    memcpy(ans,save,sizeof(ans));//save赋到res
}

void baseincrease()
{
    memset(save,0,sizeof(save));
    for(int i=1;i<=500;i++)
        for(int j=1;j<=500;j++)
            save[i+j-1]+=base[i]*base[j];
    for(int i=1;i<=500;i++)
    {
        save[i+1]+=save[i]/10;
        save[i]=save[i]%10;
    }
    memcpy(base,save,sizeof(base));//save赋到base
}

int main()
{
    base[1]=2;
    ans[1]=1;
    cin>>p;
    printf("%d\n",(int)(log10(2)*p+1));
    while(p>0)
    {
        if(p&1)
        {
            mult();
        }
        baseincrease();
        p>>=1;
    }
    ans[1]-=1;
     for(int i=500;i>=1;i-=1)
        if(i!=500&&i%50==0)printf("\n%d",ans[i]);
        else printf("%d",ans[i]);

    return 0;
}

归并排序

#include<bits/stdc++.h>
using namespace std;
#define MAX 500000
#define SENTINEL 2000000000

int L[MAX/2+2],R[MAX/2+2];
int cnt;

void merge(int A[],int n,int left,int mid,int right)
{
    int n1=mid-left;
    int n2=right-mid;
    for(int i=0;i<n1;i++) L[i]=A[left+i];
    for(int i=0;i<n2;i++) R[i]=A[mid+i];
    L[n1]=R[n2]=SENTINEL;
    int i=0,j=0;
    for(int k=left;k<right;k++)
    {
        if(L[i]<=R[j]){
            A[k]=L[i++];
        }
        else{
            A[k]=R[j++];
        }
    }
}

void mergeSort(int A[],int n,int left,int right){
    if(left+1<right){
        int mid=(left+right)/2;
        mergeSort(A,n,left,mid);
        mergeSort(A,n,mid,right);
        merge(A,n,left,mid,right);
    }
}

int main()
{
    int A[MAX],n,i;
    cnt=0;
    cin>>n;
    for(int i=0;i<n;i++) cin>>A[i];
    mergeSort(A,n,0,n);
    for(i=0;i<n;i++){
        if(i) cout<<" ";
        cout<<A[i];
    }
    return 0;
}

深搜

void dfs(会引起搜索状态改变的变量,比如坐标,比如行数)
{
    if(搜索退出的条件,比如某些条件直接失败,比如搜到终点退出记录结果)
    {
        return}
    {
        搜索的函数体
        先改变当前量,然后dfs(变量开始变化)
        回溯
    }
    return}

广搜

void bfs
{
	首结点入队列
	标记状态
	while(queue!=null)
	{
		当前节点能到的符合条件的地方入队,标记状态,继续搜索
	}
}

树状数组

```C++
int bit[MAX_N+1],n;

int sum(int i){//求1到i的和
    int s=0;
    while(i>0){
        s+=bit[i];
        i-=i&-i;
    }
    return s;
}

void add(int i,int x){//给定i和x,执行ai+=x
    while(i<=n){
        bit[i]+=x;
        i+=i&-i;
    }
}

单源最短路 Dijkstra

int cost[MAX_V][MAX_V]; //图的邻接矩阵
int d[MAX_V];//顶点s(起点)到各点的最短距离
bool used[MAX_V];//是否已经访问过
int V;//顶点数
int prev[MAX_V];//前驱顶点

void Dijkstra(int s){
    fill(d,d+V,INF);
    fill(used,used+V,false);
    fill(prev,prev+V,-1);
    d[s]=0;
    while(true){
        int v=-1;
        for(int u=0;u<V;u++)
        {
            if(!used[u]&&(v==-1||d[u]<d[v])) v=u;
        }
        if(v==-1) break;
        used[v]=true;
        for(int u=0;u<V;u++){
            if(d[u]>d[v]+cost[v][u]){
                prev[u]=v;
            }
        }
    }
}

vector<int> get_path(int t){
    vector<int> path;
    for(;t!=-1;t=prev[t]) path.push_back(t);
    reverse(path.begin(),path.end());
    return path;
}

任意两点最短路 Floyd

int d[MAX_V][MAX_V]; //无法到达时,权值为INF,主对角线为0
int V; //顶点数
void Floyd(){
    for(int k=0;k<V;k++){
        for(int i=0;i<V;i++){
            for(int j=0;j<V;j++){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}

Kruskal

#include<bits/stdc++.h>
using namespace std;
#define MAX_N 3000
#define MAX_E 4000
struct edge{
    int u,v,cost;
};
bool cmp(const edge &e1,const edge &e2){
    return e1.cost<e2.cost;
}

int par[MAX_N];
int rank[MAX_N];

void init_union_find(int n)
{
    for(int i=0;i<n;i++){
        par[i]=i;
        rank[i]=0;
    }
}

int find(int x){
    if(par[x]==x){
        return x;
    }
    else{
        return par[x]=find(par[x]);
    }
}

void unite(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x==y) return;
    if(rank[x]<rank[y])
        par[x]=y;
    else{
        par[y]=x;
        if(rank[x]==rank[y]) rank[x]++;
    }
}

edge es[MAX_E];
int V,E;

int kruskal()
{
    sort(es,es+E,cmp);
    init_union_find(V);
    int res=0;
    for(int i=0;i<=E;i++){
        edge e=es[i];
        if(find(e.u)!=find(e.v)){
            unite(e.u,e.v);
            res+=e.cost;
        }
    }
    return res;
}

int main()
{
    return 0;
}


Prim

int cost[MAX_V][MAX_V];
int mincost[MAX_V];
bool used[MAX_V];
int V;

int prim()
{
    for(int i=0;i<V;i++)
    {
        mincost[i]=INF;
        used[i]=false;
    }
    mincost[0]=0;
    int res=0;
    while(true){
        int v=-1;
        for(int u=0;u<V;u++){
            if(!used[u]&&(v==-1||mincost[u]<mincost[v])) v=u;
        }
        if(v==-1) break;
        used[v]=true;
        res+=mincost[v];
        for(int u=0;u<V;u++){
            mincost[u]=min(mincost[u],cost[v][u]);
        }
    }
    return res;
}

线段树

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=100005;
ll a[maxn+2],sum[maxn*4+2],add[maxn*4+2];

void pushup(ll p)
{
    sum[p]=sum[p*2]+sum[p*2+1];
}

void pushdown(ll p,ll total)
{
    if(add[p])
    {
        sum[p*2]+=add[p]*(total-total/2);
        sum[p*2+1]+=add[p]*(total/2);
        add[p*2]+=add[p];
        add[p*2+1]+=add[p];
        add[p]=0;
    }
}

void build(ll p,ll l,ll r)
{
    if(l==r)
    {
        sum[p]=a[l];
        return ;
    }
    ll mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    pushup(p);
}

ll query(ll p,ll be,ll ed,ll l,ll r)
{

    if(be<=l && ed>=r)
        return sum[p];
    pushdown(p,r-l+1);
    ll res=0;
    ll mid=(l+r)/2;
    if(be<=mid) res+=query(p*2,be,ed,l,mid);
    if(ed>mid) res+=query(p*2+1,be,ed,mid+1,r);
    return res;
}

void update(ll p,ll be,ll ed,ll l,ll r,ll x)
{
    if(be<=l && ed>=r){
        add[p]+=x;
        sum[p]+=(r-l+1)*x;
        return;
    }
    pushdown(p,r-l+1);
    ll mid=(l+r)/2;
    if(be<=mid) update(p*2,be,ed,l,mid,x);
    if(ed>mid) update(p*2+1,be,ed,mid+1,r,x);
    pushup(p);
}


int main()
{
    ll N,M;
    cin>>N>>M;
    for(ll i=1;i<=N;i++)
        cin>>a[i];
    build(1,1,N);
    while(M--)
    {
        ll op,x,y,k;
        cin>>op;
        if(op==1)
        {
            cin>>x>>y>>k;
            update(1,x,y,1,N,k);
        }
        if(op==2)
        {
            cin>>x>>y;
            cout<<query(1,x,y,1,N)<<endl;
        }
    }

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值