Acwing算法模板(自己用)

题目均可以在acwing中搜索得到

基础算法

二分

整数二分模板

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000010;
int a[maxn];

int main(){
    int n,q; cin>>n>>q;
    for(int i=0;i<n;i++) cin>>a[i];
    while(q--){
        int x; cin>>x;
        int l=0,r=n-1;
        while(l<r){
            int mid = l+r>>1;
            if(a[mid]>=x) r=mid;
            else l=mid+1;
        }
        if(a[l]!=x)cout<<"-1 -1\n";
        else{
            cout<<l<<" ";
            l=0,r=n-1;
            while(l<r){
                int mid = l+r+1>>1;
                if(a[mid]<=x) l=mid;
                else r=mid-1;
//                cout<<mid<<" "<<l<<" "<<r<<endl;
            }
            cout<<l<<endl;
        }
    }
    return 0;
}

实数二分模板

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

int main(){
    double x;   cin>>x;
    double l=0,r=x;
    while(r-l>1e-6){
        double mid = (l+r)/2;
        if(mid*mid>=x) r=mid;
        else l=mid;
    }
    cout<<l;
    return 0;
}

求三次方

https://www.acwing.com/problem/content/792/

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

int main(){
    double n;   cin>>n;
    //确定边界
    double l=-1000,r=1000;
    while(r-l>1e-8){
        double mid=(l+r)/2;
        if(mid*mid*mid>=n) r=mid;
        else l=mid;
    }
    printf("%.6lf",l);
    return 0;
}

高精度

高精度加法

#include<bits/stdc++.h>
using namespace std;
vector<int> a,b;
vector<int> add(vector<int> &a,vector<int> &b){
    vector<int> c;
    int t=0;
    for(int i=0;i<a.size()||i<b.size();i++){
        if(i<a.size()) t+=a[i];
        if(i<b.size()) t+=b[i];
        c.push_back(t%10);
        t/=10;
    }
    if(t) c.push_back(t);
    return c;

}
int main(){
    string s1,s2;   cin>>s1>>s2;
    for(int i=s1.size()-1;i>=0;i--) a.push_back(s1[i]-'0');
    for(int i=s2.size()-1;i>=0;i--) b.push_back(s2[i]-'0');
    auto c = add(a,b);
    for(int i=c.size()-1;i>=0;i--) cout<<c[i];
    return 0;
}

高精度减法

#include<bits/stdc++.h>
using namespace std;
vector<int> a,b;
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;
}

vector<int> sub(vector<int> &a,vector<int> &b){
    vector<int> c;
    for(int i=0,t=0;i<a.size();i++){
        t=a[i]-t;
        if(i<b.size()) t-=b[i];
        c.push_back((t+10)%10);
        if(t<0) t=1;
        else t=0;
    }
    //去除前导零
    while(c.size()>1&&c.back()==0) c.pop_back();
    return c;

}
int main(){
    string s1,s2;   cin>>s1>>s2;
    for(int i=s1.size()-1;i>=0;i--) a.push_back(s1[i]-'0');
    for(int i=s2.size()-1;i>=0;i--) b.push_back(s2[i]-'0');

    if(cmp(a,b)){
        auto c = sub(a,b);
        for(int i=c.size()-1;i>=0;i--) cout<<c[i];
    }else{
        auto c = sub(b,a);
        cout<<"-";
        for(int i=c.size()-1;i>=0;i--) cout<<c[i];
    }
    return 0;
}

高精度乘法

#include<bits/stdc++.h>
using namespace std;
vector<int> mul(vector<int> &a,int b){
    vector<int> c;
    int t=0;
    for(int i=0;i<a.size()||t;i++){
        if(i<a.size()) t+=a[i]*b;
        c.push_back(t%10);
        t/=10;
    }
    //去除前导 0 
    while (c.size() > 1 && c.back() == 0) c.pop_back();
    return c;
}
int main(){
    string s1;  cin>>s1;
    int b;  cin>>b;
    vector<int> a;
    for(int i=s1.size()-1;i>=0;i--) a.push_back(s1[i]-'0');
    
    auto c = mul(a,b);
    for(int i=c.size()-1;i>=0;i--) cout<<c[i];
    
    return 0;
}

高精度除法

#include<bits/stdc++.h>
using namespace std;
vector<int> div(vector<int> &a,int b,int &r){
    vector<int> c;
    for(int i=a.size()-1;i>=0;i--){//对A从最高位开始处理
        r=r*10+a[i];//将上次的余数*10在加上当前位的数字,便是该位需要除的被除数
        c.push_back(r/b);//所得即为商在这一位的数字
        r%=b;
    }
    //由于在除法运算中,高位到低位运算,因此C的前导零都在vector的前面而不是尾部
    //,vector只有删除最后一个数字pop_back是常数复杂度,而对于删除第一位没有相应的库函数可以使用
    //    而且删除第一位,其余位也要前移,
    //因此我们将C翻转,这样0就位于数组尾部,可以使用pop函数删除前导0
    reverse(c.begin(),c.end());
    while(c.size()>1&&c.back()==0) c.pop_back();
    return c;
}
int main(){
    string s1;  cin>>s1;
    int b;  cin>>b;
    vector<int> a;
    for(int i=s1.size()-1;i>=0;i--) a.push_back(s1[i]-'0');
    int r=0;
    auto c = div(a,b,r);
    for(int i=c.size()-1;i>=0;i--) cout<<c[i];
    cout<<endl<<r;
    return 0;
}

前缀和

一维的

#include<bits/stdc++.h>
using namespace std;
int a[1000010];
int main(){
    int n,m;    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        int t;  scanf("%d",&t);
        a[i]=a[i-1]+t;
    }
    while(m--){
        int l,r;    scanf("%d%d",&l,&r);
        printf("%d",a[r]-a[l-1]);
        if(m!=0) printf("\n");
    }
    return 0;
}

二维的

eg: 子矩阵的和

#include<bits/stdc++.h>
using namespace std;
int n,m,q;
const int maxn = 1010;
int a[maxn][maxn],s[maxn][maxn];
int main(){
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        scanf("%d",&a[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
        }
    while(q--){
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);
    }
    return 0;
}

差分

一维的

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000010
int a[maxn],b[maxn];
int n,m;
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];  
        b[i]=a[i]-a[i-1];
    }
    //牢记 a数组是 b数组的前缀和
    while(m--){
        int l,r,c;  cin>>l>>r>>c;
        b[l]+=c;//
        b[r+1]-=c;//这里要记得-c  不然 a 数组后面的会被多 +c
    }
    for(int i=1;i<=n;i++){
        a[i]=a[i-1]+b[i];//这里就是移项了一下
        cout<<a[i]<<" ";
    }
    return 0;
}

双指针

最长不重读子序列

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000010
int n,ans=0;
int a[maxn],b[maxn];
int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    for(int i=0,j=0;i<n;i++){
        b[a[i]]++;
        while(j<i&&b[a[i]]>1) b[a[j++]]--;
        ans=max(ans,i-j+1);
    }
    cout<<ans;
    return 0;
}

基础数据结构

单调队列

#include<bits/stdc++.h>
using namespace std;
int n,k,hh=0,tt=-1;// hh 单调队列头部  tt 单调队列尾部
int a[1000010],q[1000010];// q数组用来装 单调队列元素的下标
int main(){
    cin.tie(0);
    cin>>n>>k;
    for(int i=0;i<n;i++){
        cin>>a[i];
        //检查是否超过窗口长度  若超过则 对头 +1
        if(i-q[hh]+1>k) hh++;
        //判断队列是否单调递增(求的是窗口最小值,队头则是最小值) 
        while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
        q[++tt]=i;
        if(i+1>=k) cout<<a[q[hh]]<<" ";
    }
    cout<<endl;
    hh=0,tt=-1;
    for(int i=0;i<n;i++){
        if(i-q[hh]+1>k) hh++;
        while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
        q[++tt]=i;
        if(i+1>=k) cout<<a[q[hh]]<<" ";
    }
    return 0;
}

单调栈

#include<bits/stdc++.h>
using namespace std;
int tot,sta[1000010];

int main(){
    cin.tie(0);
    int n;  cin>>n;
    while(n--){
        int x;  cin>>x;
        while(tot&&sta[tot]>=x) tot--;
        if(!tot) cout<<"-1 ";
        else cout<<sta[tot]<<" ";
        sta[++tot] = x;
    }

    return 0;
}

单链表

#include<bits/stdc++.h>
using namespace std;
#define maxn 100010
int head,nex[maxn],val[maxn],idx;
void init(){
	head = -1;
	idx=0;
}
//插入到头结点
void insertHead(int x){
	val[idx] = x;
	nex[idx]=head;
	head = idx++;
} 
//将x插到下标是k的点后面
void insertOne(int k,int x){
	val[idx] = x;
	nex[idx] = nex[k];
	nex[k] = idx++;
} 
// 将下标是k的点后面的点删掉
void remove(int k){
	nex[k] = nex[nex[k]];
}
int main(){
	init();
	int m;	cin>>m;
	while(m--){
		string s;	cin>>s;
	
		if(s=="H"){
			int x; cin>>x;
			insertHead(x);
		}else{
			if(s=="D"){
				int k;	cin>>k;
				if (!k) head = nex[head];
				remove(k-1);
			}else{
				int k,x;	cin>>k>>x;
				insertOne(k-1,x);
			}
		}
	}
	for(int i=head;i != -1 ;i = nex[i]){
		cout<<val[i]<<" ";
	}
	return 0;
}

Trie树

快速存储字符集合和快速查询字符集合

//Trie树快速存储字符集合和快速查询字符集合
#include <iostream>

using namespace std;

const int N = 100010;
//son[][]存储子节点的位置,分支最多26条;
//cnt[]存储以某节点结尾的字符串个数(同时也起标记作用)
//idx表示当前要插入的节点是第几个,每创建一个节点值+1
int son[N][26], cnt[N], idx;
char str[N];

void insert(char *str)
{
    int p = 0;  //类似指针,指向当前节点
    for(int i = 0; str[i]; i++)
    {
        int u = str[i] - 'a'; //将字母转化为数字
        if(!son[p][u]) son[p][u] = ++idx;   //该节点不存在,创建节点
        p = son[p][u];  //使“p指针”指向下一个节点
    }
    cnt[p]++;  //结束时的标记,也是记录以此节点结束的字符串个数
}

int query(char *str)
{
    int p = 0;
    for(int i = 0; str[i]; i++)
    {
        int u = str[i] - 'a';
        if(!son[p][u]) return 0;  //该节点不存在,即该字符串不存在
        p = son[p][u]; 
    }
    return cnt[p];  //返回字符串出现的次数
}

int main()
{
    int m;
    cin >> m;

    while(m--)
    {
        char op[2];
        scanf("%s%s", &op, &str);

        if(*op == 'I') insert(str);
        else printf("%d\n", query(str));
    }

    return 0;
}

作者:四谷夕雨
链接:https://www.acwing.com/solution/content/14695/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

并查集

image-20220110201648998

合并集合

#include<iostream>

using namespace std;
const int N = 1e5 + 10;
int p[N];

int find(int x) {
    if (x == p[x])  //如果x是祖先,则返回
        return x;
    else
        return find(p[x]);  //如果x不是祖先,则x的爸爸 问 x的爷爷
}

//路径压缩的find函数
int find2(int x) {
    if (x != p[x])  // x不是自身的父亲,即x不是该集合的代表
        p[x] = find(p[x]);  // 查找x的祖先直到找到代表,于是顺手路径压缩
    return p[x];
}

int main() {
    int n, m;
    cin >> n >> m;

    // 记录某个人的爸爸是谁,特别规定,祖先的爸爸是他自己
    for (int i = 1; i <= n; i++) p[i] = i;
    while (m--) {
        string op;
        cin >> op;
        int a, b;
        cin >> a >> b;
        if (op == "M") p[find(a)] = find(b);
        else if (op == "Q") {
            if (find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
}

作者:松鼠爱葡萄
链接:https://www.acwing.com/solution/content/19232/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

连通块中点的数量

#include<bits/stdc++.h>
using namespace std;
int n,m;
int p[1000010];
int sz[1000010];
int findx(int x){
    if(x!=p[x]) p[x]=findx(p[x]);
    return p[x];
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        p[i]=i;
        sz[i]=1;
    }
    while(m--){
        string s;   cin>>s;
        if(s=="C"){
            int a,b;    cin>>a>>b;
            if(findx(a)==findx(b))  continue;
            sz[findx(b)]+=sz[findx(a)];
            p[findx(a)]=findx(b);
        }else
            if(s=="Q1"){
                int a,b;    cin>>a>>b;
                if(findx(a)==findx(b))  cout<<"Yes\n";
                else cout<<"No\n";
            }else{
                int a;  cin>>a;
                cout<<sz[findx(a)]<<"\n";
            }   
    }
    return 0;
}

image-20220112153723611

堆排序

//如何手写一个堆?完全二叉树 5个操作
//1. 插入一个数         heap[ ++ size] = x; up(size);
//2. 求集合中的最小值   heap[1]
//3. 删除最小值         heap[1] = heap[size]; size -- ;down(1);
//4. 删除任意一个元素   heap[k] = heap[size]; size -- ;up(k); down(k);
//5. 修改任意一个元素   heap[k] = x; up(k); down(k);
#include <iostream>

using namespace std;

int const N = 100010;

//h[i] 表示第i个结点存储的值,i从1开始,2*i是左子节点,2*i + 1是右子节点
//size 既表示堆里存储的元素个数,又表示最后一个结点的下标
int h[N], siz; //堆有两个变量h[N],size; 怎么这里的size和文件里有冲突,只能改成siz了

void down(int u)
{
    int t = u;//t存储三个结点中存在的最小的结点的下标,初始化为当前结点u
    if (u * 2 <= siz && h[u * 2] < h[t]) t = u * 2; // 左子节点存在并且小于当前结点,更新t的下标
    if (u * 2 + 1 <= siz && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//右子节点存在并且小于当前结点,更新t的下标
    if (t != u)//如果t==u意味着不用变动,u就是三个结点中拥有最小值的结点下标,否则交换数值
    {
        swap(h[t], h[u]);
        down(t); //交换数值后,t这个结点存储原本u的值,u存储存储t的值(三个数中的最小值)。u不用调整了,但t情况不明,可能需要调整。直到它比左右子节点都小
    }
}

int main()
{
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]); 
    siz = n; //初始化size,表示堆里有n 个元素

    for (int i = n / 2; i; i --) down(i); //把堆初始化成小根堆,从二叉树的倒数第二行开始,把数字大的下沉

    while (m -- )
    {
        printf("%d ", h[1]);
        h[1] = h[siz];
        siz --;
        down(1);
    }

    return 0;
}

作者:现代修士o_O
链接:https://www.acwing.com/solution/content/46535/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

模拟堆

#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;

const int N = 1e5 + 10;

int h[N];           /* 堆                                                   */
int ph[N];          /* 存储第k个插入的数在堆中的下标                        */
int hp[N];          /* 存储堆中指定下标的值是第k个插入的数,与ph[N]互逆      */
int cnt;            /* 存储当前堆中用到了哪个下标,用于插入与删除等操作      */

void heap_swap(int a, int b)
{
    swap(ph[hp[a]], ph[hp[b]]); /* 1.先通过下标a找到h[a]在堆中是第几个插入的数
                                   2.再通过第几个插入的数k找到在ph中所记录的在
                                    堆中对应的下标 3.最后与b交换            */
    swap(hp[a], hp[b]);         /* 交换所记录的在堆中插入的值的顺序         */
    swap(h[a], h[b]);           /* 交换堆中的值                             */
}

void down(int u)
{
    int t = u;
    if (u * 2 <= cnt && h[u * 2] < h[t]) 
        t = u * 2;
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) 
        t = u * 2 + 1;

    if (u != t)                 /* 如果u不是当前以u为根节点的堆的最小值     */
    {
        heap_swap(u , t);
        down(t);
    }
}

void up(int u)
{
    while (u / 2 && h[u] < h[u / 2]) {
        heap_swap(u, u / 2);
        u >>= 1;
    }
}

int main()
{
    int n, m = 0;
    cin >> n;
    while (n--) {
        char op[5];
        int k, x;
        cin >> op;
        if (!strcmp(op, "I")) {
            cin >> x;
            cnt ++;
            m ++;
            ph[m] = cnt;    /* 堆中第m个插入的数在堆中的下标是cnt   */
            hp[cnt] = m;    /* 堆中下标为cnt的数是在堆中是第m个插入的   */
            h[cnt] = x;
            up(cnt);
        }
        else if (!strcmp(op, "PM"))
            cout << h[1] << endl;
        else if (!strcmp(op, "DM")) {
            heap_swap(1, cnt);
            cnt --;
            down(1);
        }
        else if (!strcmp(op, "D")) {
            cin >> k;
            k = ph[k];                  /* 取得第k个插入的数在堆中的下标        */
            heap_swap(k, cnt);
            cnt --;
            up(k);
            down(k);
        }
        else if (!strcmp(op, "C")) {
            cin >> k >> x;
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }

    }

    return 0;
}

作者:17835208153
链接:https://www.acwing.com/solution/content/29198/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

搜索

DFS

全排列

#include<bits/stdc++.h>
using namespace std;
int n;
int a[10];
bool b[10];
int dfs(int x){
    if(x==n){
        for(int i=0;i<n;i++) cout<<a[i]<<" ";
        cout<<"\n";
        return 0;
    }
    for(int i=1;i<=n;i++){
        if(!b[i]){
            a[x]=i;
            b[i]=true;
            dfs(x+1);
            b[i]=false;
        }
    }
}
int main(){
    cin>>n;
    dfs(0);
    return 0;
}

n皇后

#include<bits/stdc++.h>
using namespace std;
const int N=10;
char ans[N][N];
int n;
int col[N],dg[N],udg[N];
void dfs(int u){
    if(n==u){
        for(int i=0;i<n;i++) puts(ans[i]);
        return ;
    }
    for(int i=0;i<n;i++){
        if(!col[i]&&!dg[n+i]&&!udg[n-i+u]){
            ans[u][i]='Q';
            col[i]=dg[n+i]=udg[n-i+u]=true;
            dfs(u+1);
            ans[u][i]='.';
            col[i]=dg[n+i]=udg[n-i+u]=false;
        }
    }
}
int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            ans[i][j]='.';
    dfs(0);

    return 0;
}

n皇后原始版

#include<bits/stdc++.h>
using namespace std;
char ans[10][10];
int n;
int row[10],col[10],dg[10],udg[10];
void dfs(int x,int y,int s){
   
    if(y==n) y=0,x++;
    if(x==n){
        if(s==n){
            for(int i=0;i<n;i++) puts(ans[i]);
            puts("");
        }
        return ;
    }
    //不放皇后
    dfs(x,y+1,s);
    //放皇后
    if(!row[x]&&!col[y]&&!dg[x+y]&&!udg[x-y+n]){
        ans[x][y]='Q';
        row[x]=col[y]=dg[x+y]=udg[x-y+n]=true;
        dfs(x,y+1,s+1);
        row[x]=col[y]=dg[x+y]=udg[x-y+n]=false;
        ans[x][y]='.';
    }
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            ans[i][j]='.';
    dfs(0,0,0);
    return 0;
}

树的重心

树的重心

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5 + 10; //数据范围是10的5次方
const int M = 2 * N; //以有向图的格式存储无向图,所以每个节点至多对应2n-2条边

int h[N]; //邻接表存储树,有n个节点,所以需要n个队列头节点
int e[M]; //存储元素
int ne[M]; //存储列表的next值
int idx; //单链表指针
int n; //题目所给的输入,n个节点
int ans = N; //表示重心的所有的子树中,最大的子树的结点数目

bool st[N]; //记录节点是否被访问过,访问过则标记为true

//a所对应的单链表中插入b  a作为根 
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// dfs 框架
/*
void dfs(int u){
    st[u]=true; // 标记一下,记录为已经被搜索过了,下面进行搜索过程
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(!st[j]) {
            dfs(j);
        }
    }
}
*/

//返回以u为根的子树中节点的个数,包括u节点
int dfs(int u) {
    int res = 0; //存储 删掉某个节点之后,最大的连通子图节点数
    st[u] = true; //标记访问过u节点
    int sum = 1; //存储 以u为根的树 的节点数, 包括u,如图中的4号节点

    //访问u的每个子节点
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        //因为每个节点的编号都是不一样的,所以 用编号为下标 来标记是否被访问过
        if (!st[j]) {
            int s = dfs(j);  // u节点的单棵子树节点数 如图中的size值
            res = max(res, s); // 记录最大联通子图的节点数
            sum += s; //以j为根的树 的节点数
        }
    }

    //n-sum 如图中的n-size值,不包括根节点4;
    res = max(res, n - sum); // 选择u节点为重心,最大的 连通子图节点数
    ans = min(res, ans); //遍历过的假设重心中,最小的最大联通子图的 节点数
    return sum;
}

int main() {
    memset(h, -1, sizeof h); //初始化h数组 -1表示尾节点
    cin >> n; //表示树的结点数

    // 题目接下来会输入,n-1行数据,
    // 树中是不存在环的,对于有n个节点的树,必定是n-1条边
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a); //无向图
    }

    dfs(1); //可以任意选定一个节点开始 u<=n

    cout << ans << endl;

    return 0;
}

作者:松鼠爱葡萄
链接:https://www.acwing.com/solution/content/13513/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

BFS

迷宫

#include<bits/stdc++.h>
using namespace std;
int n,m;
typedef pair<int,int> PII; //用来存点,用pair方便点点
const int N=110; //数据范围
int g[N][N],d[N][N];// g数组装整个图,  d数组表示某点到原点的距离

int bfs(){

    queue<PII> q;
    q.push({0,0});//首先起点入队
    memset(d,-1,sizeof d);//将最开始的所有点到起点的距离都设置为-1
    d[0][0]=0;//起点到起点的距离设置为0,
    int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0}; /*方向数组,随便向那个方向扩展都得行,
    我的是上(x不变,y会加1)右(x会加1,y不变)下(x不变,y会减1)左(x会减1,y不变)*/
    while(!q.empty()){
        auto t = q.front();//取出队首元素
        q.pop();//队首出队
        for(int i=0;i<4;i++){//向4个方向扩展
            // x,y 为扩展后的, t装的是拓展前的坐标
            int x=t.first+dx[i];
            int y=t.second+dy[i];
            //满足边界条件,拓展的点的位置没有障碍,在之前没有被访问过(距离为-1就表示没访问过)
            if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==-1){
                d[x][y]=d[t.first][t.second]+1;//更新距离
                q.push({x,y});//将成功扩展的点入队
            }
        }
    }
    return d[n-1][m-1];//最终 d[n-1][m-1] 就是右下角到左上角的需要移动的最短距离
}
int main(){
    cin>>n>>m;
    //读入图的信息
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            cin>>g[i][j];

    cout<<bfs()<<"\n";//输出结果

}

有向图的拓扑序列

有向图的拓扑序列

#include<bits/stdc++.h>
using namespace std;
int n, m;
const int N = 1000010;
int h[N], ne[N], e[N], idx;
int d[N];//d数组表示某点的入度
void add(int a,int b){//存储图,以邻接表的方式
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
int q[N];//定义个队列(数组模拟队列),装入度为0的点。 (这也可以不用队列,就看做是个普通的数组也行)
int topSe(){
    int hh = 0, tt = 0;//初始化队头和队尾
    for (int i = 1; i <= n;i++){
        if(!d[i]) q[tt++] = i; //把入度为0的点入队
    }
    while(hh<=tt){//队列不空就执行下面的
        int t = q[hh++];//取出队头的数,然后将对头的数出队。(这就是数组模拟队列的优点吧)
        for (int i = h[t]; i != -1;i=ne[i]){//遍历头结点的出边
            int j = e[i];//获取到达的点
            d[j]--;//将出边能到达的点,把入度减1(就是把边删掉)
            if(d[j]==0){//如果结点的入度为0
                d[j] = d[t] + 1;//距离 在上一个点的距离基础上 +1 
                q[tt++] = j;//将这个入度为0的点加入队列  
            }
        }
    }
    return tt == n;//队列长度等于n,返回1,否则返回0
}
int main(){
    memset(h, -1, sizeof(h));//将h数组全部置为-1,(这个看自己的写法,和上面得第20行有关)
    cin >> n >> m;
    for (int i = 0; i < m;i++){
        int a, b;
        cin >> a >> b;
        add(a, b);
        d[b]++;//因为是 a -> b ,所以,b 的入度要加 1
    }
    if(topSe()){//如果存在拓扑序列,则输出
        for (int i = 0; i <n;i++)//我们可以发现,队列中的入队顺序就是拓扑序列
            cout << q[i] << " ";
        cout << "\n";
    }else  //不存在则输出-1
        cout << "-1\n";
    return 0;
}

图论

最短路

dijkstra朴素版

#include<bits/stdc++.h>
using namespace std;
#define maxn 510
//g数组存的是邻接矩阵,d数组存的是某点到起点的最短距离,
//st数组是用来装确定了的点(确定了到起点的距离最短的点)
int g[maxn][maxn],d[maxn],st[maxn];
int n,m;
int dijkstra(){
    memset(d,0x3f,sizeof(d));//先将所有距离设为最大
    d[1]=0;//起点到起点的距离为0
    for(int i=0;i<n;i++){//找出每个点到起点的距离,每次寻找不在st中距离最近的点t
        int t=-1;//用于更新第一个点,这个比较巧妙
        for(int j=1;j<=n;j++){//寻找不在st中,距离最近的点t
            if(!st[j]&&(t==-1||d[j]<d[t])) t=j;
        }
        st[t]=true;//将t加入到st中,表示已经确定了这个点
        for(int j=1;j<=n;j++){//通过t点来更新其它点的距离
            d[j]=min(d[j],d[t]+g[t][j]);
        }
    }
    d[n]!=0x3f3f3f3f?cout<<d[n]:cout<<-1;//输出答案
    return 0;
}
int main(){
    cin>>n>>m;
    memset(g,0x3f,sizeof(g));//因为是求的最小值,所以初始化为无穷大
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=min(g[a][b],c); //在重边中去取得最小值
    }
    dijkstra();//调用
    return 0;
}

dijkstra堆优化版

#include<iostream>
#include<cstring>
#include<queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 100010; // 把N改为150010就能ac

// 稀疏图用邻接表来存
int h[N], e[N], ne[N], idx;
int w[N]; // 用来存权重
int dist[N];
bool st[N]; // 如果为true说明这个点的最短路径已经确定

int n, m;

void add(int x, int y, int c)
{
    w[idx] = c; // 有重边也不要紧,假设1->2有权重为2和3的边,再遍历到点1的时候2号点的距离会更新两次放入堆中
    e[idx] = y; // 这样堆中会有很多冗余的点,但是在弹出的时候还是会弹出最小值2+x(x为之前确定的最短路径),并
    ne[idx] = h[x]; // 标记st为true,所以下一次弹出3+x会continue不会向下执行。
    h[x] = idx++;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof(dist));
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap; // 定义一个小根堆
    // 这里heap中为什么要存pair呢,首先小根堆是根据距离来排的,所以有一个变量要是距离,其次在从堆中拿出来的时    
    // 候要知道知道这个点是哪个点,不然怎么更新邻接点呢?所以第二个变量要存点。
    heap.push({ 0, 1 }); // 这个顺序不能倒,pair排序时是先根据first,再根据second,这里显然要根据距离排序
    while(heap.size())
    {
        PII k = heap.top(); // 取不在集合S中距离最短的点
        heap.pop();
        int ver = k.second, distance = k.first;

        if(st[ver]) continue;
        st[ver] = true;

        for(int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i]; // i只是个下标,e中在存的是i这个下标对应的点。
            if(dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({ dist[j], j });
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);

    while (m--)
    {
        int x, y, c;
        scanf("%d%d%d", &x, &y, &c);
        add(x, y, c);
    }

    cout << dijkstra() << endl;

    return 0;
}

作者:optimjie
链接:https://www.acwing.com/solution/content/6554/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

bellman-ford

有边数限制的最短路

#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 10010;
struct Edge{
    int a, b, w;
} e[M];//把每个边保存下来
int dist[N], backup[N];//backup数组是备份数组,防止串联
int n, m, k;
int bellman_ford(){
    memset(dist, 0x3f, sizeof(dist));
    dist[1] = 0;
    for (int i = 0; i < k;i++){
        memcpy(backup, dist, sizeof(dist));
        for (int j = 0; j < m;j++){//遍历所有边
            int a = e[j].a, b = e[j].b, w = e[j].w;
            dist[b] = min(dist[b], backup[a] + w);
        }
    }
    return dist[n] >= 0x3f3f3f3f / 2 ? -2 : dist[n];
}
int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin >> n >> m >> k;
    for (int i = 0; i < m;i++){
        int a, b, w;
        cin >> a >> b >> w;
        e[i] = {a, b, w};
    }
    int ans = bellman_ford();
    if(ans!=-2) cout << ans;
    else cout << "impossible";
    return 0;
}

SPFA

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int h[N],ne[N],e[N],w[N],idx;
int dist[N],st[N];
int n,m;
void add(int a,int b,int c){
    w[idx]=c;
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
int spfa(){
    memset(dist,0x3f,sizeof(dist));
    dist[1]=0;
    queue<int> q;
    q.push(1);
    st[1]=true;
    while(q.size()){
        int t = q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                if(!st[j]){
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return dist[n]==0x3f3f3f3f?-2:dist[n];
}
int main(){
    cin>>n>>m;
    memset(h,-1,sizeof(h));
    while(m--){
        int a,b,w;
        cin>>a>>b>>w;
        add(a,b,w);
    }
    int ans=spfa();
    if(ans!=-2) cout<<ans;
    else cout<<"impossible";
    return 0;
}

SPFA判断是否存在负环

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int dist[N],st[N],ct[N];
int h[N],e[N],ne[N],w[N],idx;
int n,m;
void add(int a,int b,int c){
    w[idx]=c;
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
int spfa(){
    queue<int> q;
    for(int i=1;i<=n;i++){
        q.push(i);
        st[i]=true;
    }
    while(q.size()){
        int t=q.front();
        q.pop();
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                ct[j]=ct[t]+1;
                if(ct[j]>n) return true;
                if(!st[j]){
                    q.push(j);
                    st[j]=true;
                }
            }
        }
    }
    return false;
}
int main(){
    cin>>n>>m;
    memset(h,-1,sizeof(h));
    while(m--){
        int a,b,c;  cin>>a>>b>>c;
        add(a,b,c);
    }
    if(spfa())cout<<"Yes"; 
    else cout<<"No";
    return 0;
}

Floyd算法

#include<bits/stdc++.h>
using namespace std;
const int N=210;
int d[N][N];
int n,m,k;
int INF=1e9;
void floyed(){
    for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i==j) d[i][j]=0;
            else d[i][j]=INF; 
    while(m--){
        int x,y,z;   cin>>x>>y>>z;
        d[x][y]=min(d[x][y],z);
    }
    floyed();
    while(k--){
        int x,y;   cin>>x>>y;
        int ans=d[x][y];
        if(ans>=INF/2) cout<<"impossible\n";
        else cout<<ans<<"\n";
    }
    return 0;
}

最短路总结(acwing上的)

image-20220123145833916

最小生成树

prim求最小生成树

#include<bits/stdc++.h>
using namespace std;
const int N=510;
int INF = 0x3f3f3f3f;
int dist[N],g[N][N];
bool st[N];
int n,m;
int prim(){
    int res=0;
    memset(dist,INF,sizeof(dist));
    for(int i=0;i<n;i++){
        int t=-1;
        for(int j=1;j<=n;j++){
            if(!st[j]&&(t==-1||dist[t]>dist[j])) t=j;
        }
        if(i&&dist[t]==INF) return INF;
        
        st[t]=true;
        if(i)res+=dist[t];
        for(int j=1;j<=n;j++){
            dist[j]=min(dist[j],g[t][j]);
        }
    }
    return res;
}
int main(){
    cin>>n>>m;
    memset(g,0x3f,sizeof(g));
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            if(i ==j) g[i][j] = 0;
            else g[i][j] = INF;
    while(m--){
        int a,b,c;  cin>>a>>b>>c;
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    int res = prim();
    if(res==INF) cout<<"impossible\n";
    else cout<<res<<"\n";
    return 0;
}

Kruskal算法求最小生成树

#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=200010,INF = 0x3f3f3f3f;
int n,m;
int p[N];
struct Edg{
    int a,b,w;
    // bool operator< (const Edg &W)const{
    //     return w<W.w;
    // }
}e[M];
bool cmp(Edg a,Edg b){
    return a.w<b.w;
}
int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
int kruskal(){
    sort(e,e+m,cmp);
    for(int i=1;i<=n;i++) p[i]=i;
    int res=0,cnt=0;
    for(int i=0;i<m;i++){
        int a=e[i].a,b=e[i].b,w=e[i].w;
        a=find(a),b=find(b);
        if(a!=b){
            p[a]=b;
            res+=w;
            cnt++;
        }
    }
   return cnt<n-1?INF:res;
}
int main(){
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int a,b,w;
        cin>>a>>b>>w;
        e[i]={a,b,w};
    }
    int ans=kruskal();
    if(ans==INF) cout<<"impossible\n";
    else cout<<ans<<"\n";
    return 0;s
}

二分图

染色法判定二分图

#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=200010;
int h[N],e[M],ne[M],idx;
int st[M];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u,int color){
    st[u]=color;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(!st[j]){
            if(!dfs(j,3-color)) return false;
        }else if(st[j]==color) return false;
    }
    return true;
}
int main(){
    int n,m;
    cin>>n>>m;
    memset(h,-1,sizeof(h));
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);add(b,a);
    }
    bool flag=true;
    for(int i=1;i<=n;i++){
        if(!st[i]){
            if(!dfs(i,1)){
                flag=false;
                break;
            }
        }
    }
    if(flag) cout<<"Yes\n";
    else cout<<"No\n";
    return 0;
}

匈牙利算法

二分图的最大匹配

#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 100010;
int h[N],e[M],ne[M],idx;
int n1,n2,m;
int match[N];
bool st[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int find(int x){
    for(int i=h[x];i!=-1;i=ne[i]){
        int j=e[i];
        if(!st[j]){
            st[j]=true;
            if(match[j]==0||find(match[j])){
                match[j]=x;
                return true;
            }
        }
    }
    return false;
}
int main(){
    memset(h,-1,sizeof(h));
    cin>>n1>>n2>>m;
    while(m--){
        int a,b;    cin>>a>>b;
        add(a,b);
    }
    int res=0;
    for(int i=1;i<=n1;i++){
        memset(st,false,sizeof(st));
        if(find(i)) res++;
    }
    cout<<res;
    return 0;
}
posted @ 2021-10-10 23:02  后端小知识  阅读(463)  评论(0编辑  收藏  举报
返回顶端