算法(三)图论


基础课

DFS

深度优先搜索

模板

int dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

原题链接:排列数字

#include<iostream>

using namespace std;

const int N = 10;

int n;      //存储层数
int path[N];  //存储当前排列
bool st[N];     //存储状态数组,为true说明该位置有数,为false说明该位置无数,初始化后为false

void dfs(int u)
{
    if(u == n) //达到最底层的时候即输出当前排列
    {
        for (int i=0;i < n;i ++) printf("%d ",path[i]);
        puts("");
        return;
    }
    
    for (int i = 1; i <= n;i ++ )  //每一次深搜都要进行一次遍历
        if (!st[i]) //对于每一个为true的数,只寻找后面为false的数,如1为true,2、3为false,则队列变为12,13
        {
            path[u] = i; //为当前深搜排列进行赋值
            st[i] = true; //将搜到的数置为true
            dfs(u+1); //下一层进行深度搜索
            st[i] = false; //,若下一层到了底层,则回溯节点
        }
}
int main()
{
    cin >> n;
    dfs(0); //从第0层开始搜索
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 10;
	public static int n; // 层数
	public static int path[] = new int[N]; // 当前排列
	public static boolean st[] = new boolean[N];

	public static void dfs(int u) {
		if (u == n) {
			for (int i = 0; i < n; i++)
				out.print(path[i] + " ");
			out.println();
			return;
		}

		for (int i = 1; i <= n; i++) {
			if (!st[i]) {
				path[u] = i;
				st[i] = true;
				dfs(u + 1);
				st[i] = false;
			}
		}

	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		dfs(0);
		out.flush();
	}
}

n-皇后问题

#include<iostream>
#include<algorithm>

using namespace std;
const int N = 10, M = 2*N;
bool col[N], dg[M], udg[M];
char path[N];
int n;
char g[N][N];

void dfs(int u){
    if(u == n){
        for(int i = 0 ; i < n ; i ++ ){
            for(int j = 0; j <n ; j ++ ){
                cout << g[i][j] ;
            }
            cout << endl;
        }
        cout << endl;
        return ;
    }
    
    for(int i = 0 ; i < n ; i ++ ){
        if(!col[i] && !dg[u + i] && !udg[u - i + n]){
            g[u][i] = 'Q';
            col[i] = dg[u+i] = udg[u-i+n] = true;
            dfs(u + 1);
            col[i] = dg[u+i] = udg[u-i+n] = false;
            g[u][i] = '.';
        }
    }
}


int main(){
    cin >> n;
    for(int i = 0 ; i < n ; i ++ ){
        for(int j  = 0 ; j < n ; j ++ ){
            g[i][j] = '.';
        }
    }
    dfs(0);
    return 0;
}

BFS

能走最短路径

只有边权都是1时,才能用BFS求最短路径

注意这里的方向dx[4] = {-1, 0, 1, 0}, dy[4] = {0,1,0,-1},分别是上、右、下、左

image-20230525231713519

模板:

queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
    int t = q.front();
    q.pop();

    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true; // 表示点j已经被遍 历过
            q.push(j);
        }
    }
}

原题链接:https://www.acwing.com/problem/content/846/

#include<iostream>
#include<cstring>
using namespace std;
typedef pair<int,int> PII;
const int N = 110;

int n,m;
int g[N][N]; //存储原数据
int d[N][N]; //存储路径
PII q[N*N]; //模拟队列

int bfs()
{
    int hh = 0, tt = 0; //hh为队头,tt为队尾
    q[0] = {0,0};//模拟队列
    memset(d, -1 , sizeof d);
    int dx[4] = {-1,0,1,0} , dy[4] = {0,-1,0,1};//用(-1,0),(0,1),(1,0),(0,-1)模拟点的上下移动 
    d[0][0] = 0;
    while(hh <= tt)
    {
        auto t = q[hh++];//取出队头
        for(int i = 0 ; i < 4 ; i ++ )//扩展队列
        {
            int x = t.first + dx[i], y = t.second +  dy[i];
            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[++tt] = {x,y};
            }
        }
    }
    return 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() << endl;
    return 0;
}
#include<iostream>
#include<cstring>
#include<queue>

using namespace std;
typedef pair<int,int> PII;
const int N = 110;

int n,m;
int g[N][N];
int d[N][N];
queue<PII> q;

int bfs()
{
    memset(d,-1,sizeof d);
    q.push({0,0});
    d[0][0] = 0;
    int dx[4] = {-1,0,1,0} , dy[4] = {0,-1,0,1};
    while(!q.empty())
    {
        auto t = q.front();
        q.pop();
        for(int i = 0 ; i < 4 ; i ++ )
        {
            int x = t.first + dx[i] , y = t.second + dy[i];
            if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==-1)
            {
                q.push({x,y});
                d[x][y] = d[t.first][t.second] + 1;
            }
        }
    }
    return 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() << endl;
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 110;
	public static int n, m;
	public static int g[][] = new int[N][N];
	public static int d[][] = new int[N][N];
	public static Queue<Pairs> q = new LinkedList<Pairs>();

	public static int bfs() {
		for(int i = 0; i < n ; i ++ ){
		    Arrays.fill(d[i],-1);
		}
		q.add(new Pairs(0, 0));
		d[0][0] = 0;
		int dx[] = { -1, 0, 1, 0 }, dy[] = { 0, 1, 0, -1 };
		while (!q.isEmpty()) {
			Pairs p = q.element();
			q.remove();
			for (int i = 0; i < 4; i++) {
				int x = p.first + dx[i], y = p.second + dy[i];
				if (x >= 0 && x < n && y < m && y >= 0 && g[x][y] == 0 && d[x][y] == -1) {
					q.add(new Pairs(x, y));
					d[x][y] = d[p.first][p.second] + 1;
				}
			}
		}
		return d[n - 1][m - 1];
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				g[i][j] = nextInt();
			}
		}
		int res = bfs();
		out.print(res);
		out.flush();
	}
}

class Pairs {
	int first;
	int second;

	public Pairs(int first, int second) {
		this.first = first;
		this.second = second;
	}
}

八数码

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

using namespace std;

int bfs(string start){
    string end = "12345678x";
    int dx[4] = {-1,0,1,0}, dy[4] = {0,1,0,-1};
    queue<string> q;
    unordered_map<string,int> d;
    q.push(start);
    d[start] = 0;
    
    while(!q.empty()){
        string s = q.front();
        q.pop();
        
        int dis = d[s];
        if(s == end) return dis;
        
        int k = s.find('x');
        int x = k / 3;
        int y = k % 3;
        for(int i = 0 ; i < 4 ; i ++ ){
            int a = x + dx[i], b = y + dy[i];
            if(a >= 0 && a < 3 && b >= 0 && b < 3){
                swap(s[k], s[a * 3 + b]);
                if(!d.count(s)){
                    d[s] = dis + 1;
                    q.push(s);
                }
                swap(s[k], s[a * 3 + b]);
            }
        }
    }
    
    if(d[end] == 0) return -1;
    
    return d[end];
}

int main(){
    string start;
    for(int i = 0 ; i < 9 ; i ++ ){
        char c;
        cin >> c;
        start += c;
    }
    
    cout << bfs(start) << endl;
    return 0;
}

树与图的深度优先遍历

原题链接:树的重心

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

using namespace std;
const int N = 1e5+10, M = 2*N;

int n;
int idx,e[M],ne[M],h[N];
bool st[N];
int ans = N;

void add(int a,int b){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++ ;
}

int dfs(int u){
    st[u] = true;
    int size = 0, sum = 1;
    for(int i = h[u]; i != -1 ; i = ne[i]){
        int j = e[i];
        if(!st[j]) {
            int s = dfs(j);
            size = max(size, s); //记录最大联通子图的节点数
            sum += s; //以j为根的树的节点数
        }
    }
    
    size = max(size , n - sum);
    ans = min(size, ans);
    return sum;
}

int main(){
    memset(h,-1,sizeof h);
    cin >> n;
    for(int i = 0 ; i < n - 1  ; i ++ ){
        int a,b;
        cin >> a >> b;
        add(a,b);
        add(b,a);
    }
    dfs(1); 
    cout << ans <<endl;
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 100010, M = 2 * N;
	public static int n, idx, res = N;
	public static int h[] = new int[N];
	public static int e[] = new int[M];
	public static int ne[] = new int[M];
	public static boolean st[] = new boolean[N];

	public static void add(int a, int b) {
		e[idx] = b;
		ne[idx] = h[a];
		h[a] = idx++;
	}

	public static int dfs(int u) {
		st[u] = true;
		int size = 0, sum = 1;
		for (int i = h[u]; i != -1; i = ne[i]) {
			int j = e[i];
			if (st[j])
				continue;
			int s = dfs(j);
			size = Math.max(size, s);
			sum += s;
		}
		size = Math.max(size, n - sum);
		res = Math.min(size, res);
		return sum;
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		Arrays.fill(h, -1);
		for (int i = 0; i < n - 1; i++) {
			int a, b;
			a = nextInt();
			b = nextInt();
			add(a, b);
			add(b, a);
		}
		dfs(1);
		out.print(res);
		out.flush();
	}
}

树与图的广度优先遍历

原题链接:图中点的层次

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

using namespace std;
const int N = 1e5 +10;
int n,m;
int idx,e[N],ne[N],h[N],d[N];

void add(int a,int b){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++ ;
}


int bfs(){
    memset(d,-1,sizeof d);
    queue<int> q;
    q.push(1);
    d[1] = 0;
    
    while(!q.empty()){
        int t = q.front();
        q.pop();
        
        for(int i = h[t] ; i != -1; i = ne[i]){
            int j = e[i];
            if(d[j] == -1){
                d[j] = d[t] + 1;
                q.push(j);
            }
        }
    }
    return d[n];
}

int main(){
    memset(h,-1,sizeof h);
    cin >> n >> m;
    for(int i = 0 ; i < m ; i ++ ){
        int a,b;
        cin >> a >> b;
        add(a,b);
    }
    
    cout << bfs() << endl;
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 100010;
	public static int n, m, idx;
	public static int h[] = new int[N];
	public static int e[] = new int[N];
	public static int ne[] = new int[N];
	public static int d[] = new int[N];
	public static Queue<Integer> q = new LinkedList<Integer>();

	public static void add(int a, int b) {
		e[idx] = b;
		ne[idx] = h[a];
		h[a] = idx++;
	}

	public static int bfs() {
		Arrays.fill(d, -1);
		d[1] = 0;
		q.add(1);
		while (!q.isEmpty()) {
			int t = q.element();
			q.remove();
			for (int i = h[t]; i != -1; i = ne[i]) {
				int j = e[i];
				if (d[j] == -1) {
					d[j] = d[t] + 1;
					q.add(j);
				}
			}
		}
		return d[n];
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		Arrays.fill(h, -1);
		for (int i = 0; i < m; i++) {
			int a, b;
			a = nextInt();
			b = nextInt();
			add(a, b);
		}
		int res = bfs();
		out.print(res);
		out.flush();
	}
}

拓扑序列

模板:

// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点,e[k]存储该边的目的节点,ne[N]存储所有的边,
int h[N], e[N], ne[N], idx;

// 添加一条边a->b
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

// 初始化
idx = 0;
memset(h, -1, sizeof h);

bool topsort()
{
    int hh = 0, tt = -1;

    // d[i] 存储点i的入度,即存储所有入度为0的点
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            q[ ++ tt] = i;

    while (hh <= tt)
    {
        int t = q[hh ++ ];

        for (int i = h[t]; i != -1; i = ne[i]) //遍历该点的所有边
        {
            int j = e[i];
            if (-- d[j] == 0)
                q[ ++ tt] = j;
            //遍历每个目的节点,若度数减1后为0,则将该点插入到拓扑队列中
        }
    }

    // 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
    return tt == n - 1;
}
public static int idx, cnt;
public static int h[] = new int[N];
public static int e[] = new int[N];
public static int ne[] = new int[N];
public static int d[] = new int[N];
public static int ans[] = new int[N];
public static Queue<Integer> q = new LinkedList<Integer>();

public static void add(int a, int b) {
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

public static boolean topsort() {
	for(int i = 1; i <= n;  i++ ) {
		if(d[i] == 0) {
			q.add(i);
		}
	}
	while(!q.isEmpty()) {
		int t = q.element();
		q.remove();
		ans[cnt++] = t;
		for(int i = h[t] ; i != -1; i= ne[i]) {
			int j = e[i];
			d[j] -- ;
			if(d[j] == 0) {
				q.add(j);
			}
		}
	}
	
	return cnt == n;
}

原题链接:有向图的拓扑序列

#include<iostream>
#include<cstring>

using namespace std;

const int N = 100010;

int idx,h[N],e[N],ne[N];
int n,m;
int q[N],d[N];

void add(int a,int b)
{
    e[idx] = b , ne[idx] = h[a], h[a] = idx ++ ;
}

bool topsort()
{
    int tt = -1 , hh = 0 ;
    for(int i = 1 ; i <= n ; i ++ )
        if(!d[i]) q[++tt] = i; 
    while(hh<=tt)
    {
        int t = q[hh++];
        for(int i = h[t] ; i != -1 ; i = ne[i])
        {
            int j = e[i];
            d[j] -- ;
            if(!d[j]) q[++tt] = j;
        }
    }
    return tt == n - 1;
}

int main()
{
    cin >> n >> m;
    memset(h,-1,sizeof h);
    for(int i = 0 ; i < m ; i ++ )
    {
        int a,b;
        cin >> a >> b;
        add(a,b);
        d[b]++;
    }
    if(topsort())
    {
        for(int i = 0 ; i < n ; i ++ ) printf("%d ",q[i]);
        puts("");
    }
    else puts("-1");
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 100010;
	public static int n, m, idx, cnt;
	public static int h[] = new int[N];
	public static int e[] = new int[N];
	public static int ne[] = new int[N];
	public static int d[] = new int[N];
	public static int ans[] = new int[N];
	public static Queue<Integer> q = new LinkedList<Integer>();

	public static void add(int a, int b) {
		e[idx] = b;
		ne[idx] = h[a];
		h[a] = idx++;
	}

	public static boolean topsort() {
		for(int i = 1; i <= n;  i++ ) {
			if(d[i] == 0) {
				q.add(i);
			}
		}
		while(!q.isEmpty()) {
			int t = q.element();
			q.remove();
			ans[cnt++] = t;
			for(int i = h[t] ; i != -1; i= ne[i]) {
				int j = e[i];
				d[j] -- ;
				if(d[j] == 0) {
					q.add(j);
				}
			}
		}
		
		return cnt == n;
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		Arrays.fill(h, -1);
		for (int i = 0; i < m ; i++) {
			int a, b;
			a = nextInt();
			b = nextInt();
			add(a, b);
			d[b] ++ ;
		}
		if(topsort()) {
			for(int i = 0 ; i < cnt ; i ++ ) out.print(ans[i] + " ");
			out.println();
		}else {
			out.print(-1);
		}
		out.flush();
	}
}

Dijkstra

朴素Dijkstra

稀疏图:邻接矩阵 稠密图:邻接表

模板:

int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // 在还未确定最短路的点中,寻找距离最小的点
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

原题链接:https://www.acwing.com/problem/content/851/

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510;

int n ,m ;
int g[N][N];
bool st[N];
int d[N];

int dijstra()
{
    memset(d, 0x3f, sizeof d);
    d[1] = 0;
    for(int i = 0 ; i < n ; i ++ )
    {
        int t = -1;
        for(int j = 1 ; j <= n ; j ++ )
        {
            if(!st[j] && (t == -1 || d[t] > d[j]))
                t = j ;
        }
        
        for(int j = 1 ; j <= n ; j ++ )
        {
            d[j] = min(d[j], d[t] + g[t][j]);
        }
        
        st[t] = true;
    }
    
    if(d[n] == 0x3f3f3f3f) return -1;
    return d[n];
}

int main()
{
    scanf("%d%d",&n,&m) ;
    memset(g,0x3f,sizeof g);
    for(int i = 0 ; i < m ; i ++ ){
        int x, y, z;
        scanf("%d%d%d",&x,&y,&z);
        g[x][y] = min(g[x][y], z);
    }
    
    int t = dijstra();
    
    printf("%d",t);
    
    return 0;
}

堆优化Dijkstra

模板

typedef pair<int, int> PII;

int n;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定

// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});      // first存储距离,second存储节点编号

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

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

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>

using namespace std;
typedef pair<int,int> PII;
const int N = 150010;

int n,m;
int idx,h[N],e[N],ne[N],w[N];
int d[N];
bool st[N]; 

void add(int a,int b,int c)
{
    e[idx] = b , w[idx] = c , ne[idx] = h[a], h[a] = idx ++ ;      
}

int dijstra()
{
    memset(d, 0x3f , sizeof d);
    d[1] = 0;
    priority_queue<PII,vector<PII>,greater<PII>> heap; //小根堆,按first从小到大
    heap.push({0,1});
    
    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second,distance = t.first; //first是距离,second是枚举的点
        
        if(st[ver]) continue;
        st[ver] = true;
        
        for(int i = h[ver]; i != -1 ; i = ne[i])
        {
            int j = e[i];
            if(d[j] > distance + w[i])
            {
                d[j] = distance + w[i];
                heap.push({d[j],j});
            }
        }
    }
    
    if(d[n] == 0x3f3f3f3f) return -1;
    return d[n];
}

int main()
{
    memset(h, -1 ,sizeof h);
    scanf("%d%d",&n,&m);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    int t = dijstra();
    
    printf("%d",t);
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 150010;
	public static int n, m, idx;
	public static int h[] = new int[N];
	public static int e[] = new int[N];
	public static int ne[] = new int[N];
	public static int w[] = new int[N];
	public static int d[] = new int[N];
	public static boolean st[] = new boolean[N];

	public static void add(int a, int b, int c) {
		e[idx] = b;
		w[idx] = c;
		ne[idx] = h[a];
		h[a] = idx++;
	}

	public static int dijsktra() {
		Arrays.fill(d, 0x3f3f3f3f);
		d[1] = 0;
		PriorityQueue<Pairs> pq = new PriorityQueue<Pairs>();
		pq.add(new Pairs(0, 1));

		while (!pq.isEmpty()) {
			Pairs t = pq.element();
			pq.remove();

			int ver = t.second, distance = t.first;

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

			for (int i = h[ver]; i != -1; i = ne[i]) {
				int j = e[i];
				if (d[j] > distance + w[i]) {
					d[j] = distance + w[i];
					pq.add(new Pairs(d[j], j));
				}
			}
		}

		if (d[n] == 0x3f3f3f3f)
			return -1;
		return d[n];
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		Arrays.fill(h, -1);
		for (int i = 0; i < m; i++) {
			int a, b, c;
			a = nextInt();
			b = nextInt();
			c = nextInt();
			add(a, b, c);
		}
		int t = dijsktra();
		out.print(t);
		out.flush();
	}

}

class Pairs implements Comparable<Pairs> {
	int first; // 距离
	int second; // 枚举的点

	public Pairs(int first, int second) {
		this.first = first;
		this.second = second;
	}

	@Override
	public int compareTo(Pairs p) {
		if (this.first != p.first) {
			return this.first - p.first;
		} else {
			return this.second - p.second;
		}
	}
}

bellman-ford

不能存在负权回路

模板:

int n, m;       // n表示点数,m表示边数
int dist[N];        // dist[x]存储1到x的最短路距离

struct Edge     // 边,a表示出点,b表示入点,w表示边的权重
{
    int a, b, w;
}edges[M];

// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
int bellman_ford()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < m; j ++ )
        {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            if (dist[b] > dist[a] + w)
                dist[b] = dist[a] + w;
        }
    }

    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    return dist[n];
}

原题链接:有边数限制的最短路

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

using namespace std;
const int M = 10010,N = 510;
int n,m,k;
struct Edge{
    int a,b,w;
}edges[M];
int d[N],backup[N];
//backup存在的意义是进行备份,防止dist数组出现串联更新的结果,简单来说就是在一次迭代时,前面的dist值发生变化,则后面的dist值会使用前面变化后的值,因此我们在每次迭代后都需要进行备份,从而使dist改变时是与备份的值相比较
bool flag; //判断答案是否正好为-1
int bellman_ford()
{
    memset(d,0x3f,sizeof d);
    
    d[1] = 0;
    
    for(int i = 0 ; i < k ; i ++ )
    {
        memcpy(backup, d, sizeof d);
        for(int j = 0 ; j < m ; j ++ )
        {
            int a = edges[j].a, b = edges[j].b , w = edges[j].w;
            d[b] = min(d[b],backup[a]+w);
        }
    }
    
    if(d[n] > 0x3f3f3f3f / 2) 
    {
        flag = true;
        return -1;
    }
    return d[n];
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i = 0 ; i < m ; i ++ )
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        edges[i] = {a,b,w};
    }
    
    int t = bellman_ford();
    
    if(flag && t == -1) puts("impossible"); 
    else printf("%d",t);
    
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 510, M = 10010;
	public static int n, m, k;// n表示点数,m表示边数
	public static int d[] = new int[N]; // d[x]存储1到x的最短路距离
	public static int backup[] = new int[N];
	public static Edge edges[] = new Edge[M];
	public static boolean flag;
	// 求1到n的最短路距离,如果无法从1走到n,则返回-1。
	public static int bellman_ford() {
		Arrays.fill(d, 0x3f3f3f3f);
		d[1] = 0;
		for (int i = 0; i < k; i++) {
			backup = Arrays.copyOf(d, d.length);
			for (int j = 0; j < m; j++) {
				int a = edges[j].a, b = edges[j].b, w = edges[j].w;
				d[b] = Math.min(d[b], backup[a] + w);
			}
		}

		if (d[n] > 0x3f3f3f3f / 2) {
			flag = true;
			return -1;
		}
		return d[n];
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		k = nextInt();
		for (int i = 0; i < m; i++) {
			int a, b, w;
			a = nextInt();
			b = nextInt();
			w = nextInt();
			edges[i] = new Edge(a, b, w);
		}
		int t = bellman_ford();
		if (flag && t == -1)
			out.println("impossible");
		else
			out.print(t);

		out.flush();
	}

}

class Edge {
	int a, b, w;

	public Edge(int a, int b, int w) {
		this.a = a;
		this.b = b;
		this.w = w;
	}
}

spfa

模板:

int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1
int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue<int> q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        auto 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])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

原题链接:spfa求最短路

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

using namespace std;
const int N = 100010;
int n,m;
int h[N], w[N], e[N], ne[N], idx;
int d[N];
bool st[N],flag; //st数组存储的是该点是否在队列当中

void add(int a,int b,int c)
{
    e[idx] = b, w[idx] = c , ne[idx] = h[a] , h[a] = idx ++;
}

int spfa()
{
    memset(d, 0x3f, sizeof d);
    d[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(d[j] > d[t] + w[i])
            {
                d[j] = d[t] + w[i];
                if(!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    
    if(d[n] > 0x3f3f3f3f/2){
        flag = true;
        return -1;
    } 
    return d[n];
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    int t = spfa();
    
    if(flag && t == -1) puts("impossible");
    else printf("%d",t);
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 100010;
	public static int n, m, idx;
	public static boolean flag;
	public static int h[] = new int[N];
	public static int w[] = new int[N];
	public static int e[] = new int[N];
	public static int ne[] = new int[N];
	public static int d[] = new int[N];
	public static boolean st[] = new boolean[N];

	public static void add(int a, int b, int c) {
		e[idx] = b;
		w[idx] = c;
		ne[idx] = h[a];
		h[a] = idx++;
	}

	public static int spfa() {
		Arrays.fill(d, 0x3f3f3f3f);
		d[1] = 0;
		Queue<Integer> q = new LinkedList<Integer>();
		q.add(1);
		st[1] = true;
		while (!q.isEmpty()) {
			int t = q.element();
			q.remove();
			st[t] = false;

			for (int i = h[t]; i != -1; i = ne[i]) {
				int j = e[i];
				if (d[j] > d[t] + w[i]) {
					d[j] = d[t] + w[i];
					if (!st[j]) {
						q.add(j);
						st[j] = true;
					}
				}
			}
		}

		if (d[n] > 0x3f3f3f3f / 2) {
			flag = true;
			return -1;
		}
		return d[n];
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		Arrays.fill(h, -1);
		for (int i = 0; i < m; i++) {
			int a, b, c;
			a = nextInt();
			b = nextInt();
			c = nextInt();
			add(a, b, c);
		}
		int t = spfa();
		if (flag && t == -1)
			out.println("impossible");
		else
			out.print(t);

		out.flush();
	}

}

原题链接:spfa判断负环

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

using namespace std;
const int N = 100010;
int n,m;
int h[N], w[N], e[N], ne[N], idx;
int d[N],cnt[N];
bool st[N],flag;

void add(int a,int b,int c)
{
    e[idx] = b, w[idx] = c , ne[idx] = h[a] , h[a] = idx ++;
}

int spfa()
{
    //因为负环的存在,所以即使每个点距离虚拟原点距离为0,负权边的存在依然可以更新某个点为更小的负值
    queue<int> q;
    for(int i = 1 ; i <= n ;  i++ )
    {
        st[i] = true;
        q.push(i);
    }
    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(d[j] > d[t] + w[i])
            {
                d[j] = d[t] + w[i];
                cnt[j] = cnt[t] + 1;
                
                if(cnt[j] >= n) return  true;
                if(!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return false;
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    if(spfa()) puts("Yes");
    else puts("No");
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 100010;
	public static int n, m, idx;
	public static boolean flag;
	public static int h[] = new int[N];
	public static int w[] = new int[N];
	public static int e[] = new int[N];
	public static int ne[] = new int[N];
	public static int d[] = new int[N];
	public static int cnt[] = new int[N];
	public static boolean st[] = new boolean[N];

	public static void add(int a, int b, int c) {
		e[idx] = b;
		w[idx] = c;
		ne[idx] = h[a];
		h[a] = idx++;
	}

	public static boolean spfa() {
		Queue<Integer> q = new LinkedList<Integer>();
		for (int i = 1; i <= n; i++) {
			st[i] = true;
			q.add(i);
		}

		while (!q.isEmpty()) {
			int t = q.element();
			q.remove();

			st[t] = false;
			for (int i = h[t]; i != -1; i = ne[i]) {
				int j = e[i];
				if (d[j] > d[t] + w[i]) {
					d[j] = d[t] + w[i];
					cnt[j] = cnt[t] + 1;
					if (cnt[j] >= n)
						return true;
					if (!st[j]) {
						q.add(j);
						st[j] = true;
					}
				}
			}
		}

		return false;

	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		Arrays.fill(h, -1);
		for (int i = 0; i < m; i++) {
			int a, b, c;
			a = nextInt();
			b = nextInt();
			c = nextInt();
			add(a, b, c);
		}
		if (spfa())
			out.print("Yes");
		else
			out.print("No");

		out.flush();
	}

}

Floyd

初始化:
    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;

// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
    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]);
}

原题链接:https://www.acwing.com/problem/content/856/

#include<iostream>
#include<cstring>

using namespace std;
const int N = 210, INF = 1e9;

int n,m,Q;
int d[N][N];

void floyd()
{
    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()
{
    scanf("%d%d%d",&n,&m,&Q);
    for(int i = 1 ; i <= n ; i ++ )
    {
        for(int j = 1 ; j <= n ; j ++ )
        {
            if(i == j) d[i][i] = 0;
            else d[i][j] = INF;
        }
    }
    
    while(m--)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        d[a][b] = min(d[a][b],w);
    }
    
    floyd();
    
    while(Q--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        if(d[a][b] > INF/2) puts("impossible");
        else printf("%d\n",d[a][b]);
    }
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 210;
	public static int n, m, Q, idx, INF = (int) 1e9;
	public static int d[][] = new int[N][N];

	public static void floyd() {
		for (int k = 1; k <= n; k++) {
			for (int i = 1; i <= n; i++) {
				for (int j = 1; j <= n; j++) {
					d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
				}
			}
		}
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		Q = nextInt();
		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-- > 0) {
			int a, b, w;
			a = nextInt();
			b = nextInt();
			w = nextInt();
			d[a][b] = Math.min(d[a][b], w);
		}
		floyd();
		while (Q-- > 0) {
			int a, b;
			a = nextInt();
			b = nextInt();
			if (d[a][b] > INF / 2)
				out.println("impossible");
			else
				out.println(d[a][b] + " ");
		}

		out.flush();
	}

}

Prim

适用于稠密图

模板:

int n;      // n表示点数
int g[N][N];        // 邻接矩阵,存储所有边
int dist[N];        // 存储其他点到当前最小生成树的距离
bool st[N];     // 存储每个点是否已经在生成树中


// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim()
{
    memset(dist, 0x3f, sizeof dist);

    int res = 0;
    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;

        if (i) res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }

    return res;
}
 

原题链接:Prim算法求最小生成树

#include<cstring>
#include<iostream>

using namespace std;

const int N = 510 , INF = 0x3f3f3f3f;

int g[N][N];
int n,m;
bool st[N];
int d[N];

int prim()
{
    memset(d, 0x3f, sizeof d);
    int res = 0;
    for(int i = 0 ; i < n ; i ++ )
    {
        int t = -1;
        for(int  j = 1 ; j <= n ; j ++ )
        {
            if(!st[j] && (t == -1 || d[t] > d[j]))
                t = j;
        }
        
        if(i && d[t] == INF) return INF;
        if(i) res += d[t];
        
        for(int j = 1; j <= n ; j ++ ) d[j] = min(d[j],g[t][j]);
        st[t] = true;
    }
    return res;
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(g,0x3f,sizeof g);
    
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        g[a][b] = g[b][a] = min(g[a][b],c);
    }
    
    int t = prim();
    
    if(t == INF) puts("impossible");
    else printf("%d",t);
    
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 510;
	public static int n, m, INF = 0x3f3f3f3f;
	public static int g[][] = new int[N][N];// 邻接矩阵,存储所有边
	public static int d[] = new int[N];// 存储其他点到当前最小生成树的距离
	public static boolean st[] = new boolean[N];// 存储每个点是否已经在生成树中

    // 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
	public static int Prim() {
		Arrays.fill(d, 0x3f3f3f3f);
		int res = 0;
		for (int i = 0; i < n; i++) {
			int t = -1;
			for (int j = 1; j <= n; j++) {
				if (!st[j] && (t == -1 || d[t] > d[j]))
					t = j;
			}

			if (i > 0 && d[t] == INF)
				return INF;
			if (i > 0)
				res += d[t];

			for (int j = 1; j <= n; j++)
				d[j] = Math.min(d[j], g[t][j]);
			st[t] = true;
		}
		return res;
	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		for(int i = 1;i <= n ;i++){
		    Arrays.fill(g[i], 0x3f3f3f3f);
		} 
		while (m-- > 0) {
			int a, b, c;
			a = nextInt();
			b = nextInt();
			c = nextInt();
			g[a][b] = g[b][a] = Math.min(g[a][b], c);
		}

		int t = Prim();
		if (t == INF)
			out.print("impossible");
		else
			out.print(t + " ");

		out.flush();
	}

}

Kruskal

适用于稀疏图

模板:

int n, m;       // n是点数,m是边数
int p[N];       // 并查集的父节点数组

struct Edge     // 存储边
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)     // 并查集核心操作
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)     // 如果两个连通块不连通,则将这两个连通块合并
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return INF;
    return res;
}

原题链接:https://www.acwing.com/problem/content/861/

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

using namespace std;

const int N = 200010;

int n,m;
int p[N];

struct Edge{
    int a,b,w;
    bool operator< (const Edge &W) const {
        return w < W.w;
    }
}edges[N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 0 ; i < m ; i ++ )
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        edges[i] = {a,b,w};
    }
    
    sort(edges,edges+m);
    
    for(int i = 1; i <= n ; i ++ ) p[i] = i;
    
    int res = 0 , cnt = 0;
    for(int i = 0; i < m ; i ++ )
    {
        int a=edges[i].a,b=edges[i].b,w=edges[i].w;
        a=find(a),b=find(b);
        if(a != b) 
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }
    
    if(cnt < n - 1) puts("impossible");
    else printf("%d",res);
    
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
	public static StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
	public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

	public static int nextInt() throws Exception {
		in.nextToken();
		return (int) in.nval;
	}

	public static int N = 200010, INF = 0x3f3f3f3f;
	public static int n, m;
	public static int p[] = new int[N];
	public static Edge edges[] = new Edge[N];

	public static int find(int x) {
		if (p[x] != x)
			p[x] = find(p[x]);
		return p[x];
	}

	public static int Kruskal() {
		Arrays.sort(edges, 0, m);
		for (int i = 1; i <= n; i++)
			p[i] = i;

		int res = 0, cnt = 0;
		for (int i = 0; i < m; i++) {
			int a = edges[i].a, b = edges[i].b, w = edges[i].w;
			a = find(a);
			b = find(b);
			if (a != b) {
				p[a] = b;
				res += w;
				cnt++;
			}
		}

		if (cnt < n - 1)
			return INF;
		return res;

	}

	public static void main(String[] args) throws Exception {
		n = nextInt();
		m = nextInt();
		for (int i = 0; i < m; i++) {
			int a, b, w;
			a = nextInt();
			b = nextInt();
			w = nextInt();
			edges[i] = new Edge(a, b, w);
		}
		int res = Kruskal();
		if (res == INF)
			out.print("impossible");
		else
			out.print(res);
		out.flush();
	}
}

class Edge implements Comparable<Edge> {
	int a, b, w;

	public Edge(int a, int b, int w) {
		this.a = a;
		this.b = b;
		this.w = w;
	}

	@Override
	public int compareTo(Edge o) {
		return this.w - o.w;
	}
}

染色法判定二分图

一个图是二分图,当且仅当图中不含奇数环

模板:

int n;      // n表示点数
int h[N], e[M], ne[M], idx;     // 邻接表存储图
int color[N];       // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色

// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{
    color[u] = c;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (color[j] == -1)
        {
            if (!dfs(j, !c)) return false;
        }
        else if (color[j] == c) return false;
    }

    return true;
}

bool check()
{
    memset(color, -1, sizeof color);
    bool flag = true;
    for (int i = 1; i <= n; i ++ )
        if (color[i] == -1)
            if (!dfs(i, 0))
            {
                flag = false;
                break;
            }
    return flag;
}

原题链接:https://www.acwing.com/problem/content/862/

#include<iostream>
#include<cstring>

using namespace std;
const int N = 100010, M = 200010;
int idx,e[M],ne[M],h[N],color[N];
int n,m;

void add(int a,int b)
{
    e[idx] = b , ne[idx] = h[a], h[a] = idx ++ ;
}

bool dfs(int u,int c)//u是点,c是颜色
{
    color[u] = c;
    for(int i = h[u];i!=-1;i=ne[i])
    {
        int j = e[i];
        if(!color[j])
        {
            if(!dfs(j,3-c)) return false; // 因为颜色从1变成2,从2变成1,所以是3-c
        }
        else if(color[j] == c) return false;
    }
    return true;
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b), add(b,a);
    }
    
    bool flag = true;
    for(int i = 1 ; i <= n ; i ++ )
    {
        if(!color[i])
        {
            if(!dfs(i,1))
            {
                flag = false;
                break;
            }
        }
    }
    if(flag) puts("Yes");
    else puts("No");
    
    return 0;
}

匈牙利算法

原题链接: https://www.acwing.com/problem/content/863/

#include<iostream>
#include<cstring>

using namespace std;
const int N = 510, M = 100010;

int idx,h[N],e[M],ne[M];
int n1,n2,m;
bool st[N];
int match[N];

void add(int a,int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool 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()
{
    scanf("%d%d%d",&n1,&n2,&m);
    memset(h,-1,sizeof h);
    
    for(int i = 0 ; i < m ; i ++ )
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    
    int res = 0 ;
    for(int i = 1 ; i <= n1 ; i ++ )
    {
        memset(st,false,sizeof st);
        if(find(i)) res ++ ;
    }
    
    printf("%d",res);
    return 0;
}

提高课(搜索)

BFS

Flood Fill

池塘计数

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1010;
typedef pair<int,int> PII;
int n,m;
char g[N][N];
bool st[N][N];
int dx[8] = {-1,-1,-1,0,0,1,1,1}, dy[8] = {-1,0,1,1,-1,0,1,-1};
void bfs(int i,int j){
    queue<PII> q;
    q.push({i,j});
    while(!q.empty()){
        auto t = q.front();
        q.pop();
        st[t.first][t.second] = true;
        for(int i = 0; i < 8 ; i ++ ){
            int x = t.first + dx[i], y = t.second + dy[i];
            if(x >= 0 && x < n && y >= 0 && y <= m && g[x][y] == 'W' && !st[x][y]){
                q.push({x,y});
                st[x][y] = true;
            }
        }
    }
    return ;
}

int main(){
    cin >> n >> m;
    for(int i = 0 ; i < n ; i ++ ){
        cin >> g[i];
    }
    int res = 0;
    for(int i = 0 ; i < n ; i ++ ){
        for(int j = 0 ; j < m ; j ++ ){
            if(!st[i][j] && g[i][j] == 'W'){
                res ++ ;
                bfs(i, j);
            }
        }
    }
    
    cout << res << endl;
    return 0;
}

城堡问题

#include<iostream>
#include<algorithm>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;

const int N = 55, M = N * N;
int n,m;
int g[N][N];
PII q[M];
bool st[N][N];


int bfs(int sx,int sy)
{
    int dx[4] = {0,-1,0,1}, dy[4] = {-1,0,1,0};
    int area = 0;
    
    int hh = 0 , tt = 0;
    q[0] = {sx,sy};
    st[sx][sy] = true;
    while(hh <= tt)
    {
        PII t = q[hh++];
        area ++;
        for(int i = 0 ; i < 4 ; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if(a<0 || a>= m || b < 0 || b >= n ) continue;
            if(st[a][b]) continue;
            if(g[t.x][t.y] >> i & 1) continue;
            
            q[++tt] = {a,b};
            st[a][b] = true;
        }
    }
    return area;
}

int main()
{
    cin >> m >> n;
    for(int i = 0 ; i < m ; i ++ )
        for(int j = 0 ; j < n ; j ++ )
            cin >> g[i][j];
            
    int cnt = 0, area = 0;
    for(int i = 0 ; i < m ; i ++ )
        for(int j = 0 ; j < n ; j ++ )
            if(!st[i][j])
            {
                area = max(area, bfs(i,j));
                cnt ++ ;
            }
            
    cout << cnt << endl;
    cout << area << endl;
    
    return 0;
}

山峰和山谷

#include<iostream>
#include<algorithm>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;

const int N = 1010, M = N * N;
int n;
int h[N][N];
PII q[M];
bool st[N][N];

void bfs(int sx,int sy,bool& has_higher, bool& has_lower)
{
    int hh = 0 , tt = 0;
    q[0] = {sx,sy};
    st[sx][sy] = true;
    
    while(hh<=tt)
    {
        PII t = q[hh++];
        
        for(int i = t.x - 1 ; i <= t.x + 1 ; i ++ )
            for(int j = t.y - 1 ; j <= t.y + 1 ; j ++ )
            {
                if(i == t.x && j == t.y) continue;
                if(i < 0 || i >= n || j < 0 || j >= n ) continue;
                if(h[i][j] != h[t.x][t.y]) //如果高度不同
                {
                    if(h[i][j] > h[t.x][t.y]) has_higher = true;
                    else has_lower = true;
                }else if(!st[i][j])
                {
                    q[++tt] = {i,j};
                    st[i][j] = true;
                }
            }
    }
}

int main()
{
    scanf("%d",&n);
    
    for(int i = 0 ; i < n ; i ++ )
        for(int j = 0 ; j < n ; j ++ )
            scanf("%d",&h[i][j]);
            
    int peak = 0, valley = 0;
    for(int i = 0 ; i < n ; i ++ )
        for(int j = 0 ; j < n ; j ++ )
            if(!st[i][j])
            {
                bool has_higher = false, has_lower = false;
                bfs(i,j,has_higher,has_lower);
                if(!has_higher) peak++; //没有比它高的说明是山峰
                if(!has_lower) valley++;
            }
            
    cout << peak << ' ' << valley << endl;
    return 0;
}

最短路模型

迷宫问题

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

using namespace std;
const int N = 1010;
typedef pair<int,int> PII;
int n;
int maze[N][N];
int dx[4] = {-1,0,1,0}, dy[4] = {0,1,0,-1};
PII pre[N][N];
void bfs(int i,int j){
    queue<PII> q;
    q.push({i,j});
    memset(pre, -1, sizeof pre);
    pre[i][j] = {0,0};
    while(!q.empty()){
        auto t = q.front();
        q.pop();
        
        for(int i = 0; i < 4 ; i ++ ){
            int x = t.first + dx[i], y = t.second + dy[i];
            if(x < 0 || x >= n || y < 0 || y >= n ) continue;
            if(pre[x][y].first != -1 || maze[x][y] == 1) continue;
            q.push({x,y});
            pre[x][y] = t;
        }     
    }
}


int main()
{
    cin >> n ;
    for(int i = 0 ; i < n ; i ++ ){
        for(int j = 0 ; j < n ; j ++ ){
            cin >> maze[i][j];
        }
    }
    
    bfs(n - 1, n - 1); //从终点遍历,可使得最后输出的点为正向
    PII end(0,0); //起点
    
    while(true){
        cout << end.first << ' ' << end.second <<endl;
        if(end.first == n - 1 && end.second == n - 1) break;
        end = pre[end.first][end.second];
    }
    
    return 0;
}

武士风度的牛

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define x first
#define y second 
using namespace std;
const int N = 155;
typedef pair<int,int> PII;

int dx[8] = {-2,-2,-1,-1,1,1,2,2}, dy[8] = {-1,1,2,-2,2,-2,1,-1};
char g[N][N];
int n, m;
int d[N][N];

void bfs(int sx,int sy){
    memset(d, -1, sizeof d);
    queue<PII> q;
    q.push({sx, sy});
    d[sx][sy] = 0 ;
    while(!q.empty()){
        auto t = q.front();
        q.pop();
        
        for(int i = 0 ; i < 8 ; i ++ ){
            int a = t.x + dx[i], b = t.y + dy[i];
            if(a < 0 || a >= n || b < 0 || b >= m) continue;
            if(d[a][b] != -1 || g[a][b] == '*') continue;
            //if(g[a][b] == 'H') return d[t.x][t.y] + 1;
            q.push({a,b});
            d[a][b] = d[t.x][t.y] + 1;
        }
    }
}

int main(){
    cin >> m >> n;
    int kx,ky,hx,hy;
    for(int i = 0 ; i < n ; i ++ )
        for(int j = 0 ; j < m ; j ++ ) 
        {
            cin >> g[i][j];
            if(g[i][j] == 'K') 
            {
                kx = i, ky = j;
            }else if(g[i][j] == 'H'){
                hx = i, hy = j;
            }            
        }

    bfs(kx, ky);
    cout << d[hx][hy] << endl;  
    return 0;
}

抓住那头牛

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

using namespace std;
const int N = 1e5 + 10;
int n,k;
int d[N];

int bfs(){
    memset(d, -1, sizeof d);
    queue<int> q;
    q.push(n);
    d[n] = 0;
    while(!q.empty()){
        auto t = q.front();
        q.pop();
        
        if(t == k) return d[k];
        if(t + 1 < N && d[t + 1] == -1){
            d[t + 1] = d[t] + 1;
            q.push(t+1);
        }
        if(t - 1 >= 0 && d[t - 1] == -1){
            d[t - 1] = d[t] + 1;
            q.push(t-1);
        }
        if(t * 2 < N && d[t * 2] == -1){
            d[t * 2] = d[t] + 1;
            q.push(t*2);
        }
    }
}

int main(){
    cin >> n >> k;
    cout << bfs() << endl;
    return 0;
}

多源bfs

矩阵距离

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define x first
#define y second
using namespace std;
const int N = 1010;
typedef pair<int,int> PII;

int n,m;
char A[N][N];
queue<PII> q;
int d[N][N];
int dx[4] = {-1,0,1,0} , dy[4] = {0,1,0,-1};
int main(){
    cin >> n >> m;
    memset(d, -1, sizeof d);
    for(int i = 0 ; i < n ; i++ ){
        for(int j = 0 ; j < m ; j ++ ){
            cin >> A[i][j];
            if(A[i][j] == '1'){
                q.push({i,j});
                d[i][j] = 0;
            }
        }
    }
    
    while(!q.empty()){
        
        auto t = q.front();
        q.pop();
        
        for(int i = 0 ; i < 4 ; i ++ ){
            int a = t.x + dx[i], b = t.y + dy[i];
            if(a < 0 || a >= n || b < 0 || b >= m) continue;
            if(d[a][b] != -1) continue;
            
            q.push({a,b});
            d[a][b] =  d[t.x][t.y] + 1;
        }
    }
    
    for(int i = 0 ; i < n ; i ++ ){
        for(int j = 0 ; j < m ; j ++){
            cout << d[i][j] << ' ' ;
        }
        cout << endl;
    }
    
    return 0;
}

最小步数模型

魔板

#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
using namespace std;

char a[10],b[10];
map<string,int> dist;//{字符串,变化次数}
map<string,pair<char,string>> pre; //{新字符串,{A/B/C,原字符串}}
string eend;//用end有歧义

string get(string t, int op)
{
    string k;
    if( op == 0 ) k = {t[7], t[6], t[5], t[4], t[3], t[2], t[1], t[0]};//'A'
    if( op == 1 ) k = {t[3], t[0], t[1], t[2], t[5], t[6], t[7], t[4]};//'B'
    if( op == 2 ) k = {t[0], t[6], t[1], t[3], t[4], t[2], t[5], t[7]};//'C'
    return k;
}

int bfs()
{
    string s = "12345678";
    queue<string> q;
    dist[s] = 0;
    q.push(s);
    
    while(!q.empty())
    {
        auto t = q.front();
        q.pop();
        if(t == eend) return dist[t];
        for(int i = 0 ; i < 3 ; i ++ )
        {
            string s = get(t,i);
            if(!dist.count(s))
            {
                dist[s] = dist[t] + 1;
                pre[s] = {'A' + i, t};
                q.push(s);
            }
        }
    }
}

int main()
{
    string start = "12345678", res;
    for(int i = 1 ; i <= 8 ; i ++ ) cin >> a[i];
    
    for(int i = 1 ; i <= 8 ; i ++ ) eend.push_back(a[i]);
    
    cout << bfs() << endl;
    while(eend != start)
    {
        res += pre[eend].first;
        eend = pre[eend].second;
    }
    
    reverse(res.begin(), res.end());
    cout << res ;
    
}

双向广搜

一般用到最小步数模型里,而不是flood fill和最短路模型里

字串变换

#include<iostream>
#include<algorithm>
#include<map>
#include<cstring>
#include<queue>

using namespace std;
const int N = 6;

int n;
string A,B; //A是起始串,B是终止串
string a[N], b[N];//a存储A字串,b存储B字串

int extend(queue<string>& q, unordered_map<string,int>& da, unordered_map<string,int>& db, string a[N], string b[N])
{
    int d = da[q.front()];
    while(q.size() && da[q.front()] == d)
    {
        auto t = q.front();
        q.pop();
        
        for(int i = 0; i < n ; i ++ )//遍历所有规则
            for(int j = 0 ; j < t.size() ; j ++ ) //遍历串
                if(t.substr(j, a[i].size()) == a[i]) //如果能够转换
                {
                    string r = t.substr(0, j) + b[i] + t.substr(j + a[i].size()); //a串换为b串
                    if(db.count(r)) return da[t] + db[r] + 1;
                    if(da.count(r)) continue;
                    da[r] = da[t] + 1;
                    q.push(r);
                }
    }
    return 11;
}

int bfs()
{
    //BFS的扩展方式是:分别枚举在原字符串中使用替换规则的起点,和所使用的的替换规则。
    if(A == B) return 0;
    queue<string> qa, qb;
    unordered_map<string, int> da,db;//分别存储转换到该串需要几次
    
    qa.push(A), qb.push(B);
    da[A] = db[B] = 0;
    
    int step = 0;
    while(qa.size() && qb.size())
    {
        int t;
        if(qa.size() < qb.size()) t = extend(qa, da, db, a, b); //从前往后扩展是a->b
        else t = extend(qb, db, da, b ,a);//从后往前扩展是b->a
        
        if(t <= 10) return t;
        if(++step == 10) return -1;
    }
    
    return -1;
}

int main()
{
    cin >> A >> B;
    while(cin >> a[n] >> b[n]) n++;
    
    int t = bfs();
    if(t == -1) puts("NO ANSWER!");
    else cout << t << endl;
    
    return 0;
}

双端队列广搜

只包含0和1的两种边权重类型的最短路问题

若扩展出来的边权重是0,则插入到队头,若为1,则插入到队尾

电路维修

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

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 510, M = N * N;

int n, m;
char g[N][N];
int dist[N][N];

int bfs()
{
    memset(dist, 0x3f, sizeof dist);
    dist[0][0] = 0;
    deque<PII> q;
    q.push_back({0,0});
    
    char cs[] = "\\/\\/" ; //存储的是\/\/,因为\是转义,所以写两个
    int dx[4] = {-1,-1,1,1}, dy[4] = {-1,1,1,-1}; //枚举每个点的对角线上的点
    int ix[4] = {-1,-1,0,0}, iy[4] = {-1,0,0,-1}; //枚举某个点的邻边
    
    while(q.size())
    {
        PII t = q.front();
        q.pop_front();
        
        for(int i = 0 ; i < 4 ; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if(a < 0 || a > n || b < 0 || b > m) continue;
            
            int ca = t.x + ix[i], cb = t.y + iy[i];
            int d = dist[t.x][t.y] + (g[ca][cb] != cs[i]);
            
            if(d < dist[a][b])
            {
                dist[a][b] = d;
                if(g[ca][cb] != cs[i]) q.push_back({a,b});//需要变化时插尾
                else q.push_front({a,b}); //不需要变化时插头
            }
        }
    }
    
    return dist[n][m];
}

int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        cin >> n >> m;
        for(int i = 0 ; i < n ; i ++ ) cin >> g[i];
        
        if(n + m & 1) puts("NO SOLUTION"); //奇数点一定无解
        else cout << bfs() << endl;
    }
    
    return 0;
}

A*

image-20221114155831954

image-20221114155854649

f[state]是state到终点的估计距离,g[state]是state到终点的真实距离

d[state]是起点到state的距离

A*算法必须保证f[state] <= g[state]

一定保证有解才可以搜,否则不如普通bfs

八数码

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <unordered_map>

using namespace std;

//估价函数:当前状态中每个数与它的目标位置的曼哈顿距离之和

int f(string state)//求曼哈顿距离
{
    int res = 0;
    for (int i = 0; i < state.size(); i ++ )
        if (state[i] != 'x')
        {
            int t = state[i] - '1';
            res += abs(i / 3 - t / 3) + abs(i % 3 - t % 3);
        }
    return res;
}

string bfs(string start)
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    char op[4] = {'u', 'r', 'd', 'l'};

    string end = "12345678x";
    unordered_map<string, int> dist; //存储实际距离
    unordered_map<string, pair<string, char>> prev;// {新串,{源串,变换记录}}
    priority_queue<pair<int, string>, vector<pair<int, string>>, greater<pair<int, string>>> heap;//小根堆

    heap.push({f(start), start});
    dist[start] = 0;

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        string state = t.second;

        if (state == end) break;

        int step = dist[state];
        int x, y;
        for (int i = 0; i < state.size(); i ++ )
            if (state[i] == 'x')
            {
                x = i / 3, y = i % 3;//找到x的横纵坐标
                break;
            }
        string source = state;
        for (int i = 0; i < 4; i ++ ) //扩展x
        {
            int a = x + dx[i], b = y + dy[i];
            if (a >= 0 && a < 3 && b >= 0 && b < 3)
            {
                swap(state[x * 3 + y], state[a * 3 + b]);
                if (!dist.count(state) || dist[state] > step + 1)
                 //除了终点之外,当第一次搜到某个点的时候,距离不一定是最短的,其dist[]的值是不断变小的,所以要加后面一个判断
                {
                    dist[state] = step + 1;
                    prev[state] = {source, op[i]};
                    heap.push({dist[state] + f(state), state});//实际距离加估算距离放入堆
                }
                swap(state[x * 3 + y], state[a * 3 + b]);
            }
        }
    }

    string res;
    while (end != start)
    {
        res += prev[end].second;
        end = prev[end].first;
    }
    reverse(res.begin(), res.end());
    return res;
}

int main()
{
    string g, c, seq;//seq存储无x的字符串
    while (cin >> c)
    {
        g += c;
        if (c != "x") seq += c;
    }

    int t = 0;//求逆序对个数
    for (int i = 0; i < seq.size(); i ++ )
        for (int j = i + 1; j < seq.size(); j ++ )
            if (seq[i] > seq[j])
                t ++ ;

    if (t % 2) puts("unsolvable");//奇数个逆序对无解
    else cout << bfs(g) << endl;

    return 0;
}

第K短路

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

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
typedef pair<int, PII> PIII;

const int N = 1010, M = 200010;

int n,m,S,T,K;
int h[N],rh[N],e[M],w[M],ne[M],idx;
int dist[N],cnt[N];
bool st[N];

void add(int h[],int a,int b,int c)
{
    e[idx] = b,w[idx] = c,ne[idx] = h[a], h[a] = idx++;
}

//估计距离:通过反向邻接表djkstra算法求出终点到所有点的最短距离
void djsktra()
{
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,T});
    
    memset(dist, 0x3f,sizeof dist);
    dist[T] = 0;
    
    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();
        
        int ver = t.y;
        if(st[ver])  continue;
        st[ver] = true;
        
        for(int i = rh[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i]; 
                heap.push({dist[j],j});
            }
        }
    }
}

int astar()
{
    priority_queue<PIII,vector<PIII>,greater<PIII>> heap;
    heap.push({dist[S],{0,S}});
    
    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();
        
        int ver = t.y.y, distance = t.y.x;
        cnt[ver] ++ ;
        if(cnt[T] == K) return distance;
        
        for(int i = h[ver]; ~i ; i = ne[i])
        {
            int j = e[i];
            if(cnt[j] < K)
                heap.push({distance + w[i] + dist[j], {distance + w[i], j}});
        }
    }
    
    return -1;
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h); //正向邻接表
    memset(rh, -1, sizeof rh); //反向邻接表
    
    for(int i = 0 ; i < m ; i ++ )
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(h, a, b, c);
        add(rh, b, a, c);
    }
    
    cin >> S >> T >> K;
    if(S == T)  K ++ ;
    
    djsktra();
    cout << astar() << endl;
    return 0;
}

DFS

连通性模型

迷宫

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

using namespace std;
const int N = 110;
int Q;
int dx[4] = {-1,0,1,0}, dy[4] = {0,1,0,-1};
bool st[N][N];
char g[N][N];
int n;
int ha, la, hb, lb;

bool dfs(int ha,int la){
    if(g[ha][la] == '#') return false;
    if(ha == hb && la == lb){
        return true;
    }

    st[ha][la] = true;
    for(int i = 0; i < 4 ; i ++ ){
        int x = ha + dx[i] , y = la + dy[i];
        
        if(x < 0 || x >= n || y < 0 || y >= n ) continue;
        if(st[x][y]) continue;
        if(dfs(x,y)) return true;
    }
    
    return false;
}


int main(){
    cin >> Q;
    while(Q--){
        memset (st,false,sizeof (st));
        cin >> n;
        for(int i = 0; i < n ; i ++ ){
            for(int j = 0; j < n ; j ++ ){
                cin >> g[i][j];
            }
        }
        cin >> ha >> la >> hb >> lb;
        if(dfs(ha,la)) cout <<"YES" << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

红与黑

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 25;
int w,h;
char g[N][N];
bool st[N][N];
int dx[4] = {-1,0,1,0}, dy[4] = {0,1,0,-1};
int dfs(int x,int y){
    int cnt = 1;
    st[x][y] = true;
    for(int i = 0 ; i < 4 ; i ++ ){
        int a = x + dx[i], b = y + dy[i];
        if(a < 0 || a >= h || b < 0 || b >= w) continue;
        if(st[a][b] || g[a][b] == '#') continue;
        cnt += dfs(a,b);
    }
    
    return cnt;
}

int main(){
    while(cin >> w >> h , w || h){
        int x,y;
        for(int i = 0 ; i < h ; i ++ )
            for(int j = 0 ; j < w ; j ++ )
                {
                    cin >> g[i][j];
                    if(g[i][j] == '@') 
                    {
                        x = i, y = j;
                    }
                }
        memset(st, 0, sizeof st);
        cout << dfs(x,y) << endl;
    }
    return 0;
}

搜索顺序

当为内部搜索,比如一个棋盘它本身的状态进行改变时,需要用到中间状态,则需要回溯;当为外部搜索,比如一个棋盘某个点到另一个点的连通性,不用考虑中间状态,则不需要回溯

马走日

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

using namespace std;
const int N = 10;
int T;
int n,m;
int x,y;
int g[N][N];
bool st[N][N];
int dx[8] = {-2,-1,1,2,2,1,-1,-2}, dy[8] = {1,2,2,1,-1,-2,-2,-1};
int res;

void dfs(int x,int y,int cnt){
    
    if(cnt == n * m){
        res ++ ;
        return ;
    }
    
    st[x][y] = true;
    
    for(int i = 0 ; i < 8 ; i ++ ){
        int a = x + dx[i], b = y + dy[i];
        if(a < 0 || a >= n || b < 0 || b >= m ) continue;
        if(st[a][b]) continue;
        
        dfs(a,b, cnt + 1);
    }
     
    st[x][y] = false;
}

int main(){
    cin >> T;
    while(T --){
        cin >> n >> m >> x >> y;
        res = 0;
        dfs(x, y, 1);
        cout << res  << endl;
    }
    
    return 0;
}

单词接龙

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 25;

int n;
string word[N];
int g[N][N];//g[i][j] 表示word[i]的后缀和word[j]的前缀的最小相同长度
int used[N];
int res;

void dfs(string dragon,int last){//last表示上个字符串下标
    res = max((int)dragon.size(), res);
    used[last] ++ ;
    for(int i = 0 ; i < n ; i ++ ){
        if(g[last][i] && used[i] < 2){
            dfs(dragon + word[i].substr(g[last][i]), i);
            //substr()函数若只有一个参数,说明是从某个位置到末尾
            //该语句是将龙头加上新的拼接字符串作为新的龙头,进而不断递归
        }
    }
    
    used[last] -- ;
} 

int main(){
    cin >> n;
    for(int i = 0 ; i < n ; i ++ ){
        cin >> word[i];
    }
    
    char start;
    cin >> start;
    for(int i = 0 ; i < n ; i ++ ){
        for(int j = 0 ; j < n ; j ++ ){
            string a = word[i], b = word[j];
            for(int k = 1 ; k < min(a.size(), b.size()); k ++ ){
                if(a.substr(a.size() - k, k) == b.substr(0,k)){
                    g[i][j] = k;
                    break;
                }
            }
        }
    }
    
    for(int i = 0 ; i < n ; i ++ ){
        if(word[i][0] == start)
            dfs(word[i], i);
    }
    
    cout << res << endl;
    return 0;
}

分成互质组

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

using namespace std;
const int N = 10;
int n;

int p[N];
int group[N][N];//group[i]表示第i组中下标对应的p数组中的数互质
bool st[N];
int res = N; //最坏情况分为N组

int gcd(int a,int b){ //判断是否互质(求最大公因数)
    return b ?gcd(b, a % b): a;
}

bool check(int group[],int gc,int i){
    for(int j = 0 ; j < gc ; j ++ ){
        if(gcd(p[group[j]], p[i]) > 1)
        return false;
    }
    return true;
}

//g表示当前做到了哪一组,gc表示当前枚举到组内的第几个元素,tc表示当前一共处理多少个数,start表示组内从哪个数开始枚举
void dfs(int g,int gc,int tc,int start){
    if(g >= res) return ;
    if(tc == n) res = g; //所有数均处理完
    
    bool flag = true;
    for(int i = start ; i < n ; i ++ ) {
        if(!st[i] && check(group[g], gc, i))
        {
            st[i] = true;
            group[g][gc] = i;
            dfs(g, gc + 1, tc + 1, i + 1);
            st[i] = false;
            flag = false;
        }
    }
    
    if(flag) dfs(g + 1,0, tc, 0); //如果没找到互质的,则开启下一组
    return ;
}

int main()
{
    cin >> n;
    for(int i = 0 ; i < n ; i ++ ) cin >> p[i];
    
    dfs(1,0,0,0);
    cout << res << endl;
    return 0;
}

剪枝与优化

小猫爬山

1 -> 猫按照重量由大到小排序,先放重猫(优先考虑决策少的方案)

3 -> 当前某个猫放到车上超出车承重,直接删去

4 -> 某个方案大于res,直接剪枝

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 20;

int cat[N], cab[N];
int n, w;
int res = N;

bool cmp(int a,int b){
    return a > b;
}

//第u条猫,第k辆车
void dfs(int u, int k){
    //最优性剪枝
    if(k >= res) return ;
    if(u == n){
        res = k;
        return ;
    } 
    
    for(int i = 0 ; i < k ; i ++ ){
        if(cab[i] + cat[u] <= w){
            cab[i] += cat[u];
            dfs(u + 1, k);
            cab[i] -= cat[u];
        }
    }
    
    //新开一辆车
    cab[k] = cat[u];
    dfs(u + 1, k + 1);
    cab[k] = 0;
}

int main(){
    cin >> n >> w;
    
    for(int i = 0 ; i < n ; i++ ) cin >> cat[i];
    
    sort(cat, cat + n, cmp);
    
    dfs(0,0);
    
    cout << res << endl;
}

数独

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

using namespace std;

const int N = 9, M = 1 << N;
//用一个二进制数表示当前行,或列或方格的状态,如100110010,则表示可填的是1,4,5,8
int ones[M], map[M];//ones保存每个状态有多少个1,map[i]保存lg(i)为多少
int row[N], col[N], cell[3][3];
char str[100];

void init()
{
    for (int i = 0; i < N; i ++ )
        row[i] = col[i] = (1 << N) - 1;

    for (int i = 0; i < 3; i ++ )
        for (int j = 0; j < 3; j ++ )
            cell[i][j] = (1 << N) - 1;
}

void draw(int x, int y, int t, bool is_set)//若is_set是true,则是在(x,y)添加t,若is_set是false,则是删去(x,y)这个数 
{
    if (is_set) str[x * N + y] = '1' + t;
    else str[x * N + y] = '.';

    int v = 1 << t;
    if (!is_set) v = -v;

    row[x] -= v;
    col[y] -= v;
    cell[x / 3][y / 3] -= v;
}

int lowbit(int x)//返回二进制的最后一位1
{
    return x & -x;
}

int get(int x, int y)//找到所有可填的二进制状态
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}

bool dfs(int cnt)
{
    if (!cnt) return true;

    int minv = 10;
    int x, y;
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            if (str[i * N + j] == '.')
            {
                int state = get(i, j);
                if (ones[state] < minv)
                {
                    minv = ones[state];
                    x = i, y = j;
                }
            }

    int state = get(x, y);
    for (int i = state; i; i -= lowbit(i))
    {
        int t = map[lowbit(i)];
        draw(x, y, t, true);
        if (dfs(cnt - 1)) return true;
        draw(x, y, t, false);
    }

    return false;
}

int main()
{
    for (int i = 0; i < N; i ++ ) map[1 << i] = i;
    for (int i = 0; i < 1 << N; i ++ )
        for (int j = 0; j < N; j ++ )
            ones[i] += i >> j & 1;

    while (cin >> str, str[0] != 'e')
    {
        init();

        int cnt = 0;
        for (int i = 0, k = 0; i < N; i ++ )
            for (int j = 0; j < N; j ++, k ++ )
                if (str[k] != '.')
                {
                    int t = str[k] - '1';
                    draw(i, j, t, true);
                }
                else cnt ++ ;

        dfs(cnt);

        puts(str);
    }

    return 0;
}

木棒

先从小到大枚举木棒的长度,对于每个长度,从前往后依此拼接木棍,从而找到合法方案

1 -> 先枚举长度更长的木棍

2 -> 排除等效冗余:(1)按照组合数的方式枚举(2)如果当前木棍加到当前棒中失败了,则直接略过后面所有长度相等的木棍

​ (3)如果木棒的第一根木棍失败了,则该方案一定失败(4)如果木棒的最后一根木棍失败了,则该方案一定失败

3 -> 只枚举木棍长度和sum的约数

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 70;

int n;
int w[N];
int sum,length;
bool st[N];

bool dfs(int u,int cur,int start) //u枚举木棒的个数,cur表示当前已经接上木棍的长度,start表示枚举的木棍下标
{
    if(u * length == sum) return true;
    if(cur == length) return dfs(u + 1, 0, 0);
    
    //剪枝3-1,i从start开始枚举
    for(int i = start; i < n ; i ++ )
    {
        if(st[i] || cur + w[i] > length) continue; //可行性剪枝
        
        st[i] = true;
        if(dfs(u,cur + w[i], i + 1)) return true;
        st[i] = false;
        
        //剪枝3-3和3-4
        if(!cur || cur + w[i] == length) return false;
        
        //剪枝3-2
        int j = i;
        while(j < n && w[j] == w[i]) j ++ ;
        i = j - 1;
    }
    
    return false;
}

int main()
{
    while(cin >> n, n)
    {
        memset(st,0,sizeof st);
        sum = 0;
        
        for(int i = 0; i < n ; i ++ )
        {
            cin >> w[i];
            sum += w[i];
        }
        
        //剪枝2,优化搜索顺序
        sort(w,w+n);
        reverse(w,w+n);
        
        length = 1;
        while(true)
        {
            //剪枝1
            if(sum % length == 0 && dfs(0,0,0))
            {
                cout << length << endl;
                break;
            }
            length ++ ;
        }
    }
    
    return 0;
}

生日蛋糕

image-20221116103052877

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 25, INF = 1e9;

int n,m;
int minv[N],mins[N];//前若干层的体积最小值和面积最小值
int R[N], H[N];//每一层的半径和高度
int res = INF;

void dfs(int u,int v,int s) //u枚举的层数,v当前枚举体积,s当前枚举表面积
{
    if(v + minv[u] > n) return;
    if(s + mins[u] >= res) return;
    if(s + 2 * (n - v) / R[u + 1] >= res) return ;
    
    if(!u)
    {
        if(v == n) res = s;
        return ;
    }
    
    for(int r = min(R[u+1] - 1,(int)sqrt(n-v)); r >= u; r --)
        for(int h = min(H[u+1] - 1,(n-v) /r /r ); h >= u;h -- )
        {
            int t = 0;
            if(u == m) t = r * r;
            R[u] = r,H[u] = h;
            dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
        }
}

int main()
{
    cin >> n >> m;
    for(int i = 1 ; i <= m ; i ++ )
    {
        minv[i] = minv[i-1] + i * i * i;
        mins[i] = mins[i-1] + 2 * i * i;
    }
    R[m + 1] = H[m + 1] = INF;
    
    dfs(m, 0, 0);
    
    if(res == INF) res = 0;
    cout << res << endl;
    return 0;
}

迭代加深

适合于某些分支特别深,但是答案在比较浅的分支的问题

加成序列

剪枝1:优先枚举较大的数

剪枝2:排除等效冗余,如果某两对数和相等,只需要枚举其中一对

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

using namespace std;

const int N = 110;
int path[N] ;
int n;

//u当前迭代的点,k当前迭代的层数
bool dfs(int u,int k){
    if(u == k) return path[u - 1] == n; 
    
    bool st[N] = {0};
    for(int i = u - 1; i >= 0 ; i -- ){
        for(int j = i ; j >= 0 ; j -- ){
            int s = path[i] + path[j];
            if(s > n || s <= path[u - 1] || st[s]) continue;
            st[s] = true;
            path[u] = s;
            if(dfs(u + 1, k)) return true;
        }
    }
    
    return false;
}

int main(){
    path[0] = 1;
    while(cin >> n , n){
        int k = 1;
        while(!dfs(1, k)) k ++ ;
        for(int i = 0 ; i < k ; i ++ ){
            cout << path[i] << ' ';
        }
        cout << endl;
    }
    
    return 0;
}

双向DFS

送礼物

其中k是指2^k与 2^(N-k)*k相接近最合适的数,这里取了n/2

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long LL;

const int N = 1 << 24;

int n, m, k;
int g[50], weights[N];
int cnt = 0;
int ans;


void dfs(int u, int s)
{
    if (u == k)
    {
        weights[cnt ++ ] = s;
        return;
    }

    if ((LL)s + g[u] <= m) dfs(u + 1, s + g[u]);
    dfs(u + 1, s);
}


void dfs2(int u, int s)
{
    if (u == n)
    {
        int l = 0, r = cnt - 1;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (weights[mid] + (LL)s <= m) l = mid;
            else r = mid - 1;
        }
        if (weights[l] + (LL)s <= m) ans = max(ans, weights[l] + s);

        return;
    }

    if ((LL)s + g[u] <= m) dfs2(u + 1, s + g[u]);
    dfs2(u + 1, s);
}


int main()
{
    cin >> m >> n;
    for (int i = 0; i < n; i ++ ) cin >> g[i];

    sort(g, g + n);
    reverse(g, g + n);

    k = n / 2;  // 防止 n = 1时,出现死循环
    dfs(0, 0);

    sort(weights, weights + cnt);
    int t = 1;
    for (int i = 1; i < cnt; i ++ )
        if (weights[i] != weights[i - 1])
            weights[t ++ ] = weights[i];
    cnt = t;

    dfs2(k, 0);

    cout << ans << endl;

    return 0;
}
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
typedef long long LL;
const int N = 46;

int n,m,k;
int w[N];
int weights[1 << 25], cnt = 1;
int ans; //全局最小值

//u是当前枚举到的数,s是当前的和
void dfs1(int u,int s){
    if(u == k){
        weights[cnt ++ ] = s;
        return ;
    }
    
    dfs1(u + 1, s) ; //不选当前的物品
    if((LL)s + w[u] <= m) dfs1(u + 1, s + w[u]); //选当前物品
}

void dfs2(int u,int s){
    if(u >= n){ //二分求出w - s的最大值
        int l = 0 , r = cnt - 1;
        while(l < r){
            int mid = l + r + 1 >> 1;
            if(weights[mid] <= m - s) l = mid;
            else r = mid - 1;
        }
        
        ans = max(ans, weights[l] + s);
        return ;
    }
    
    dfs2(u + 1, s);
    if((LL) s + w[u] <= m) dfs2(u + 1, s + w[u]);
}

int main()
{
    cin >> m >> n;
    for(int i = 0 ; i < n ; i ++ ) cin >> w[i];
    
    sort(w, w + n);
    reverse(w, w + n);
    
    k = n >> 1;
    dfs1(0, 0);
    
    sort(weights, weights + cnt);
    cnt = unique(weights, weights + cnt) - weights; //判重
    
    dfs2(k, 0);
    
    cout << ans << endl;
    
    return 0;
}

IDA*

排书

估价函数:total/3上取整(total存的是错误后继的数量,后面一个数比前面一个大1就是一个正确的后继

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 15;

int n;
int q[N]; // 存储该序列
int w[5][N]; // 存储现场,方便恢复现场

int f() // 估价函数
{
    int cnt = 0;
    for(int i = 0 ; i + 1 < n ; i ++ )
        if(q[i + 1] != q[i] + 1)
            cnt ++ ;
    return (cnt + 2) / 3;
}

bool check() //检查是否有错误后继
{
    for(int i = 0; i + 1 < n ; i ++ )
        if(q[i + 1] != q[i] + 1)
            return false;
    return true;
}

bool dfs(int depth, int max_depth)//当前迭代深度,最大迭代深度
{
    if (depth + f() > max_depth) return false;
    if (check()) return true;

    for (int len = 1; len <= n; len ++ )
        for (int l = 0; l + len - 1 < n; l ++ )
        {
            int r = l + len - 1;
            for (int k = r + 1; k < n; k ++ )
            {
                memcpy(w[depth], q, sizeof q);
                int x, y;
                for (x = r + 1, y = l; x <= k; x ++, y ++ ) q[y] = w[depth][x];
                for (x = l; x <= r; x ++, y ++ ) q[y] = w[depth][x];
                if (dfs(depth + 1, max_depth)) return true;
                memcpy(q, w[depth], sizeof q);
            }
        }

    return false;
}

int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        cin >> n;
        for(int i = 0 ; i < n ; i ++ ) cin >> q[i];
        int depth = 0;
        while(depth < 5 && !dfs(0,depth)) depth ++ ;
        if(depth >= 5) puts("5 or more");
        else cout << depth << endl;
    }
    
    return 0;
}

回转游戏

估价函数:设中间八个数重复次数最多的数是s次,则其估价函数为8-s

/*
      0     1
      2     3
4  5  6  7  8  9  10
      11    12
13 14 15 16 17 18 19
      20    21
      22    23
*/

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

using namespace std;

const int N = 24;

int op[8][7] = {
    {0, 2, 6, 11, 15, 20, 22},
    {1, 3, 8, 12, 17, 21, 23},
    {10, 9, 8, 7, 6, 5, 4},
    {19, 18, 17, 16, 15, 14, 13},
    {23, 21, 17, 12, 8, 3, 1},
    {22, 20, 15, 11, 6, 2, 0},
    {13, 14, 15, 16, 17, 18, 19},
    {4, 5, 6, 7, 8, 9, 10}
};

int oppsite[8] = {5, 4, 7, 6, 1, 0, 3, 2};
int center[8] = {6, 7, 8, 11, 12, 15, 16, 17};

int q[N];
int path[100];

int f() //估价函数
{
    static int sum[4];
    memset(sum, 0, sizeof sum);
    for (int i = 0; i < 8; i ++ ) sum[q[center[i]]] ++ ;

    int maxv = 0;
    for (int i = 1; i <= 3; i ++ ) maxv = max(maxv, sum[i]);

    return 8 - maxv;
}

void operate(int x) //模拟八个操作
{
    int t = q[op[x][0]];
    for (int i = 0; i < 6; i ++ ) q[op[x][i]] = q[op[x][i + 1]];
    q[op[x][6]] = t;
}

bool dfs(int depth, int max_depth, int last) //当前深度,最大深度,上个操作
{
    if (depth + f() > max_depth) return false;
    if (f() == 0) return true;

    for (int i = 0; i < 8; i ++ )
        if (last != oppsite[i])
        {
            operate(i);
            path[depth] = i;
            if (dfs(depth + 1, max_depth, i)) return true;
            operate(oppsite[i]);
        }

    return false;
}

int main()
{
    while (cin >> q[0], q[0])
    {
        for (int i = 1; i < 24; i ++ ) cin >> q[i];

        int depth = 0;
        while (!dfs(0, depth, -1)) depth ++ ;

        if (!depth) printf("No moves needed");
        else
        {
            for (int i = 0; i < depth; i ++ ) printf("%c", 'A' + path[i]);
        }
        printf("\n%d\n", q[6]);
    }

    return 0;
}

提高课(图论)

单源最短路的建图方式

热浪

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 2510, M = 2*6210;
int n,m,ts,te;
int idx,e[M],ne[M],w[M],h[N];
int d[N];
bool st[N];

void add(int a,int b,int c){
    e[idx] = b,w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra()
{
    memset(d, 0x3f, sizeof d);
    d[ts] = 0;
    priority_queue<PII, vector<PII>,greater<PII>> heap;
    heap.push({0,ts});
    
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second, dis = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        
        for(int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];
            if(d[j] > dis + w[i]){
                d[j] = dis + w[i];
                heap.push({d[j], j});
            }
        }
    }
    
    return d[te];
}

int main()
{
    memset(h, -1,sizeof h);
    cin >> n >> m >> ts >> te;
    for(int i = 0 ; i < m ; i ++ )
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
        add(b,a,c);
    }
    
    int t = dijkstra();
    cout << t << endl;
    return 0;
}

信使

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

using namespace std;
typedef pair<int,int> PII;
const int N = 110, M = 210 * 2;
int idx,e[M],ne[M],w[M],h[N];
int n,m;
bool st[N];
int d[N];

void add(int a,int b,int c){
    e[idx] = b, w[idx] = c,ne[idx] = h[a], h[a] = idx++;
}

int dijkstra(){
    memset(d, 0x3f, sizeof d);
    d[1] = 0;
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0, 1});
    
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second, dis = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        
        for(int i = h[ver]; i != -1 ; i = ne[i]){
            int j = e[i];
            if(d[j] > dis + w[i]){
                d[j] = dis + w[i];
                heap.push({d[j], j});
            }
        }
    }
    int res;
    for(int i = 1 ; i <= n ; i ++ ){
        if(d[i] > 0x3f3f3f3f /2 ) return -1;
        res = max(res, d[i]);
    }
    return res;
}

int main()
{
    memset(h, -1,sizeof h);
    cin >> n >> m ;
    for(int i = 0 ; i <m ; i ++ ){
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    
    int t = dijkstra();
    cout << t << endl;
    return 0;
}

香甜的黄油

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 810, M = 1460 * 2;
int n,p,c;
int idx,e[M],ne[M],w[M],h[N];
bool st[N];
int d[N];
int num[N];

void add(int a,int b,int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int dijkstra(int s){
    
    memset(st, false, sizeof st);
    memset(d, 0x3f, sizeof d);
    d[s] = 0;
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,s});
    
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second, dis = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        
        for(int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];
            if(d[j] > dis + w[i]) {
                d[j] = dis + w[i];
                heap.push({d[j], j});
            }
        }
    }
    int ans = 0;
    for(int i = 0 ; i < n ; i ++  ){
        if(d[num[i]] > 0x3f3f3f3f /2 ) return 0x3f3f3f3f;
        ans += d[num[i]];
    }

    return ans;
}

int main(){
    memset(h, -1,sizeof h);
    cin >> n >> p >> c;
    for(int i = 0 ; i < n ; i ++ ) cin >> num[i];
    for(int i = 0 ; i < c ; i ++ ){
        int x,y,z;
        cin >> x >> y >> z;
        add(x,y,z);
        add(y,x,z);
    }
    int res = 0x3f3f3f3f;
    for(int i = 1;  i <= p ; i ++ )  res = min(res,dijkstra(i));
    cout << res << endl;
    return 0;
}

最小花费

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<double,int> PII;
const int N  = 2010, M = 1e5 * 2 + 10;
int idx,e[M], ne[M], w[M], h[N];
int n, m, A, B;
bool st[N];
double d[N];

void add(int a,int b,int c){
    e[idx] = b ,w[idx] = c, ne[idx] = h[a], h[a] = idx++ ;
} 

double dijkstra(){
    for(int i = 1 ; i <= n ;  i++ ) d[i] = 0x3f3f3f3f;
    d[B] = 100;
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({100,B});
    
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second;
        double dis = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        for(int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];
            if(d[j] > dis  * 100 /(100 - w[i])){
                d[j] = dis * 100 /(100 - w[i]);
                heap.push({d[j], j});
            }
        }
    }
    
    return d[A];
}

int main(){
    cin >> n >> m;
    memset(h ,-1, sizeof h);
    for(int i = 0 ; i < m ; i ++ ){
        int a,b,c;
        cin >> a >> b >> c;
        add(b, a, c);
        add(a, b, c);
    }
    cin >> A >> B;
    double t = dijkstra();
    printf("%.8lf\n", t);
    return 0;
}

最优方案

#include<iostream>
#include<cstring>
#include<algorithm>
#include<sstream>
#include<queue>
using namespace std;
const int N = 510, M = 110;

int n,m;
bool g[N][N];
int d[N];
bool st[N];
int stop[N];

void bfs(){
    memset(d, 0x3f, sizeof d);
    queue<int> q;
    q.push(1);
    d[1] = 0 ;
    while(!q.empty()){
        auto t = q.front();
        q.pop();
        
        for(int i = 1 ; i <= n ; i ++){
            if(g[t][i] && d[i] > d[t] + 1){
                d[i] = d[t] + 1;
                q.push(i);
            }
        }
    }
}

int main()
{
    cin >> m >> n ;
    string line;
    getline(cin,line); //读入换行符
    for(int i = 0 ; i < m ; i ++ ){
        getline(cin ,line);
        stringstream ssin(line);
        int cnt = 0, p ;
        while(ssin >> p ) stop[cnt ++ ] = p;
        for(int j = 0 ; j < cnt ; j ++ ){
            for(int k = j + 1 ; k < cnt ; k ++ ){
                g[stop[j]][stop[k]] = true;
            }
        }
    }
    
    bfs();
    
    if (d[n] == 0x3f3f3f3f) puts("NO");
    else cout << max(d[n] - 1,0) << endl;
    return 0;
}

昂贵的聘礼

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int m,n;
int g[N][N];
int d[N];
int level[N];
bool st[N];

int dijkstra(int lmin, int lmax){
    memset(d, 0x3f, sizeof d);
    memset(st, false,sizeof st);
    d[0] = 0;
    for(int i = 0; i < n ; i ++ ){
        int t = -1;
        for(int j = 0 ; j <= n ; j ++ ){
            if(!st[j] && (t == -1 || d[t] > d[j]))
                t = j;
        }
        
        for(int j = 1 ; j <= n ; j ++ ){
            if(level[j] >= lmin && level[j] <= lmax){
                d[j] = min(d[j], d[t] + g[t][j]);
            }
        }
        st[t] = true;
    }
    
    return d[1];
}

int main(){
    memset(g, 0x3f, sizeof g);
    for(int i = 1 ; i <= n ;i ++ ){
        g[i][i] = 0;
    }
    cin >> m >> n;
    for(int i = 1 ; i <= n ; i ++ ){
        int p, l, x ;
        cin >> p >> l >> x;
        g[0][i] = p;
        level[i] = l;
        while(x -- ){
            int t, v;
            cin >> t >> v;
            g[t][i] = v;
        }
    }
    int res = INF;
    for(int i = level[1] - m ; i <= level[1] ; i ++ ) res = min(res, dijkstra(i, i + m));
    
    cout << res << endl;
    return 0;
}

单源最短路的综合应用

新年好

单源最短路+dfs暴搜

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

using namespace std;
typedef pair<int,int> PII;
const int N = 50010, M = 1e5 * 2 + 10, INF = 0x3f3f3f3f;
int idx, e[M], ne[M], w[M], h[N];
int n,m;
bool st[N];
int d[6][N];
int source[6];

void add(int a,int b,int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void dijkstra(int start, int dist[]){
    memset(st, false, sizeof st);
    memset(dist, 0x3f, N * 4);
    priority_queue<PII,vector<PII>, greater<PII>> heap;
    heap.push({0,start});
    dist[start] = 0;
    
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second, dis = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        
        for(int i = h[ver] ; i != -1; i = ne[i]){
            int j = e[i];
            if(dist[j] > dis + w[i]){
                dist[j] = dis + w[i];
                heap.push({dist[j], j});
            }
        }
    }
}

//u:枚举到第几个亲戚,start:从哪个亲戚出发,dis:当前的距离
int dfs(int u,int start,int dis){
    if(u == 6) return dis;
    
    int res = INF;
    for(int i = 1; i <= 5 ; i++){
        if(!st[i]){
            int next = source[i];
            st[i] = true;
            res = min(res,  dfs(u + 1, i, dis + d[start][next]));
            st[i] = false;
        }
    }
    
    return res;
}


int main(){
    cin >> n >> m ;
    memset(h ,-1,sizeof h);
    source[0] = 1;
    for(int i = 1 ; i <= 5 ;  i++ ) cin >> source[i];
    for(int i = 0 ; i < m ; i ++ ) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    
    for(int i = 0 ; i < 6 ; i ++) dijkstra(source[i], d[i]);
    
    memset(st, false ,sizeof st);
    int res = dfs(1, 0, 0);
    cout << res << endl;
    return 0;
}

通信线路
双端队列广搜+二分

image-20230502215526896

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

using namespace std;
const int N = 1010, M = 20010;
int n,m,k;
int idx, e[M], ne[M], w[M], h[N];
int d[N];
bool st[N];
deque<int> q;

void add(int a,int b,int c){
    e[idx] = b, w[idx]= c, ne[idx]= h[a], h[a] = idx ++;
}

bool check(int bound)
{
    //求最小的这样一个x,使得大于等于x的边的数量<=k
    //双端队列广搜求大于等于x的边的数量
    memset(st, 0, sizeof st);
    memset(d, 0x3f, sizeof d);
    d[1] = 0;
    q.push_back(1);
    
    while(q.size()){
        auto t = q.front();
        q.pop_front();
        
        if(st[t]) continue;
        st[t] = true;
        
        for(int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i], x = w[i] > bound;
            if(d[j] > d[t] + x){
                d[j] = d[t] + x;
                if(x) q.push_back(j);
                else q.push_front(j);
            }
        }
    }
    
    return d[n] <= k;
}

int main(){
    cin >> n >> m >> k;
    memset(h, -1, sizeof h);
    for(int i = 0 ;i < m ;i ++ ){
        int a,b,c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    
    int l = 0, r = 1e6 +1  ;
    while(l < r){
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    
    if (r == 1e6 + 1) cout << -1 << endl;
    else cout << r << endl;
}

道路与航线

dfs + 拓扑 + 最短路

image-20230504205514350

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef pair<int,int> PII;
const int N = 25010, M = 150010, INF = 0x3f3f3f3f;

int n, mr, mp, S, bnt;
int idx, e[M], ne[M], w[M], h[N];
bool st[N];
int d[N];
int id[N];
int din[N];
vector<int> block[N];
queue<int> q;

void add(int a,int b,int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u,int bid){
    block[bid].push_back(u);
    id[u] = bid;
    
    for(int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        if(!id[j]){
            dfs(j, bid);
        }
    }
}

void dijkstra(int bid){
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    for(auto it : block[bid]){
        heap.push({d[it], it});
    }
    
    while(heap.size()){
        auto t = heap.top();
        heap.pop();
        
        int ver = t.second, dis = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        
        for(int i = h[ver]; i!= -1; i = ne[i]){
            int j = e[i];
            if(d[j] > dis + w[i]){
                d[j] = dis + w[i];
                if(id[j] == id[ver]) heap.push({d[j], j});
            }
            
            if(id[ver] != id[j] && --din[id[j]] == 0) q.push(id[j]);
        }
    }
}

void topsort(){
    memset(d, 0x3f, sizeof d);
    d[S] = 0 ;
    
    for(int i = 1 ; i <= bnt ; i ++){
        if(!din[i]){
            q.push(i);
        }
    }
    
    while(q.size()){
        auto t = q.front();
        q.pop();
        
        dijkstra(t);
    }
}

int main()
{
    cin >> n >> mr >> mp >> S;
    memset(h, -1, sizeof h);
    for(int i = 0 ; i < mr ; i ++ ) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    
    for(int i = 1 ; i <= n ; i ++) {
        if(!id[i]){
            dfs(i, ++ bnt);
        }
    }
    
    for(int i = 0 ; i < mp ; i ++) {
        int a,b,c;
        cin >> a>> b >> c;
        add(a, b, c);
        din[id[b]] ++ ;
    }
    
    topsort();
    
    for(int i = 1 ; i <= n ; i ++ ){
        if(d[i] > INF / 2) cout << "NO PATH" << endl;
        else cout << d[i] << endl;
    }
    
    return 0;
}

最优贸易(未做)

dp + 最短路

先求出:

  • 从 1走到 i 的过程中,买入水晶球的最低价格 dmin[i];
  • 从 i 走到 n 的过程中,卖出水晶球的最高价格 dmax[i];

然后枚举每个城市作为买卖的中间城市,求出 dmax[i] - dmin[i] 的最大值即可。

求 dmin[i] 和 dmax[i] 时,由于不是拓扑图,状态的更新可能存在环,因此不能使用动态规划,只能使用求最短路的方式。

不能用dijsktra算法的原因:

最一般的最短路维护的是路径上的sum性质,本题维护的是max和min性质,sum性质具有累加性(就是要从前面的值基础上累加,后续出现只会越来越大,所以第一次出现的就是最短),而max 和min对于新出现的数,单独比较即可,所以不能用dijkstra(dijkstra就是利用的sum的累加性)

总的来说就是max和min,后面出现的数不一定比前面的数都差(而dijkstra的sum性质能保证后面出现的数都比前面的数都差)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 100010, M = 2000010;

int n, m;
int price[N];
int h[N], rh[N], e[M], ne[M], idx;
int dmin[N], dmax[N];
bool st[N];

void add(int *h, int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void spfa(int *d, int start, int *h, bool flag)
{
    queue<int> q;
    memset(st, 0, sizeof st);

    if (flag) memset(d, 0x3f, sizeof dmin);

    q.push(start);
    st[start] = true;
    d[start] = price[start];

    while (q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (flag && d[j] > min(d[t], price[j]) || !flag && d[j] < max(d[t], price[j]))
            {
                if (flag) d[j] = min(d[t], price[j]);
                else d[j] = max(d[t], price[j]);

                if (!st[j])
                {
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);

    for (int i = 1; i <= n; i ++ ) scanf("%d", &price[i]);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(h, a, b), add(rh, b, a);
        if (c == 2) add(h, b, a), add(rh, a, b);
    }

    spfa(dmin, 1, h, true);
    spfa(dmax, n, rh, false);

    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, dmax[i] - dmin[i]);

    printf("%d\n", res);

    return 0;
}

单源最短路的扩展方式

选择最佳线路

虚拟源点

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

using namespace std;
typedef pair<int,int> PII;
const int N = 1010, M = 30010, INF = 0x3f3f3f3f;

int n,m,s;
int idx, e[M], ne[M], w[M], h[N];
bool st[N];
int d[N];
int cnt;

void add(int a,int b,int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra(){
    memset(d, 0x3f, sizeof d);
    memset(st, false, sizeof st);
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    d[0] = 0;
    heap.push({0, 0});
    
    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int ver = t.second, dis = t.first;
        if(st[ver]) continue;
        st[ver] = true;
        
        for(int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];
            if(d[j] > dis + w[i]){
                d[j] = dis + w[i];
                heap.push({d[j], j});
            }
        }
    }
    
    if(d[s] > INF / 2) return -1;
    
    return d[s];
}

int main()
{
    while(cin >> n >> m >> s){
        memset(h, -1, sizeof h);
        idx = 0;
        for(int i = 0 ; i < m ; i ++ ){
            int a,b,c;
            cin >> a >> b >> c;
            add(a, b, c);
        }
        cin >> cnt ;
        for(int i = 0; i < cnt ; i ++ ){
            int p;
            cin >> p;
            add(0, p, 0);
        }
        int t = dijkstra();
        cout << t << endl;
    }
    
    return 0;
}

拯救大兵瑞恩(未做)

#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
#include <set>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 11, M = 360, P = 1 << 10;

int n, m, k, p;
int h[N * N], e[M], w[M], ne[M], idx;
int g[N][N], key[N * N];
int dist[N * N][P];
bool st[N * N][P];

set<PII> edges;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void build()
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            for (int u = 0; u < 4; u ++ )
            {
                int x = i + dx[u], y = j + dy[u];
                if (!x || x > n || !y || y > m) continue;
                int a = g[i][j], b = g[x][y];
                if (!edges.count({a, b})) add(a, b, 0);
            }
}

int bfs()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1][0] = 0;

    deque<PII> q;
    q.push_back({1, 0});

    while (q.size())
    {
        PII t = q.front();
        q.pop_front();

        if (st[t.x][t.y]) continue;
        st[t.x][t.y] = true;

        if (t.x == n * m) return dist[t.x][t.y];

        if (key[t.x])
        {
            int state = t.y | key[t.x];
            if (dist[t.x][state] > dist[t.x][t.y])
            {
                dist[t.x][state] = dist[t.x][t.y];
                q.push_front({t.x, state});
            }
        }

        for (int i = h[t.x]; ~i; i = ne[i])
        {
            int j = e[i];
            if (w[i] && !(t.y >> w[i] - 1 & 1)) continue;   // 有门并且没有钥匙
            if (dist[j][t.y] > dist[t.x][t.y] + 1)
            {
                dist[j][t.y] = dist[t.x][t.y] + 1;
                q.push_back({j, t.y});
            }
        }
    }

    return -1;
}

int main()
{
    cin >> n >> m >> p >> k;

    for (int i = 1, t = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            g[i][j] = t ++ ;

    memset(h, -1, sizeof h);
    while (k -- )
    {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        int a = g[x1][y1], b = g[x2][y2];

        edges.insert({a, b}), edges.insert({b, a});
        if (c) add(a, b, c), add(b, a, c);
    }

    build();

    int s;
    cin >> s;
    while (s -- )
    {
        int x, y, c;
        cin >> x >> y >> c;
        key[g[x][y]] |= 1 << c - 1;
    }

    cout << bfs() << endl;

    return 0;
}

最短路计数

最短路的条数

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 1e5 + 10, M = 4e5 + 10, MOD = 100003;

int n,m;
int idx, e[M], ne[M], h[N];
int d[N];
int cnt[N];

void add(int a,int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void bfs()
{
    memset(d, 0x3f, sizeof d);
    queue<int> q;
    q.push(1);
    d[1] = 0;
    cnt[1] = 1;
    
    while(q.size()){
        auto t = q.front();
        q.pop();
        
        for(int i = h[t]; i != -1; i = ne[i]){
            int j = e[i];
            if(d[j] > d[t] + 1){
                d[j] = d[t] + 1;
                cnt[j] = cnt[t];
                q.push(j);
            }
            else if(d[j] == d[t] + 1){
                cnt[j] = (cnt[t] + cnt[j]) % MOD;
            }
        }
    }
}

int main()
{
    memset(h , -1, sizeof h);
    cin >> n >> m ;
    for(int i = 0; i < m ; i ++ ){
        int a,b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }
    
    bfs();
    
    for(int i = 1; i <= n ; i ++ ) cout << cnt[i] << endl;
    return 0;
}

观光

最短路径的条数

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

using namespace std;
const int N = 1010, M = 10010;
int n,m,S,F;
int idx, e[M], ne[M], h[N], w[M];
int d[N][2];
bool st[N][2];
int cnt[N][2];

struct Ver {
    int ver, type, dist; //0:最短路,1:次短路
    //小根堆重载大于号
    bool operator> (const Ver &W) const{
        return dist > W.dist;
    }
};

void add(int a,int b,int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra(){
    memset(st, 0, sizeof st);
    memset(d, 0x3f, sizeof d);
    memset(cnt, 0, sizeof cnt);
    
    d[S][0] = 0, cnt[S][0] = 1;
    priority_queue<Ver, vector<Ver>, greater<Ver>> heap;
    heap.push({S, 0, 0});
    
    while(heap.size()){
        Ver t = heap.top();
        heap.pop();
        
        int ver = t.ver, type = t.type, dis = t.dist, count = cnt[ver][type];
        if(st[ver][type]) continue;
        st[ver][type] = true;
        
        for(int i = h[ver]; i != -1 ;i = ne[i]){
            int j = e[i];
            if(d[j][0] > dis + w[i]){
                //最短路变为次短路
                d[j][1] = d[j][0];
                cnt[j][1] = cnt[j][0];
                heap.push({j, 1, d[j][1]});
                //更新最短路
                d[j][0] = dis + w[i];
                cnt[j][0] = count;
                heap.push({j, 0, d[j][0]});
            }
            else if(d[j][0] == dis + w[i]){
                cnt[j][0] += count;
            }else if(d[j][1] > dis + w[i]){
                //更新次短路
                d[j][1] = dis + w[i];
                cnt[j][1] = count;
                heap.push({j, 1, d[j][1]});
            }else if(d[j][1] == dis + w[i]){
                cnt[j][1] += count;
            }
        }
    }
    
    int res = cnt[F][0];
    if(d[F][0] + 1 == d[F][1]) res += cnt[F][1];
    
    return res;
}

int main(){
    int cases;
    cin >> cases;
    while(cases--){
        memset(h , -1, sizeof h);
        idx = 0;
        cin >> n >> m ;
        for(int i = 0; i < m ; i++ ){
            int a,b,c;
            cin >> a >> b >> c;
            add(a, b, c);
        }
        
        cin >> S >> F;
        cout << dijkstra() << endl;
    }
    return 0;
}

Floyd算法

牛的旅行

最短路

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define  x first
#define  y second

using namespace std;
typedef pair<int,int> PII;
const int N = 160;
const double INF = 1e20;

int n;
double d[N][N];
PII q[N];
char g[N][N];
double maxd[N];

double get_dist(PII p1, PII p2){
    int dx = p1.x - p2.x, dy = p1.y - p2.y;
    return sqrt(dx * dx + dy * dy);
}


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

int main()
{
    cin >> n;
    for(int i = 0 ; i < n ; i ++ ) cin >> q[i].x >> q[i].y;
    for(int i = 0 ; i < n ; i ++ ) cin >> g[i];
    for(int i = 0 ; i < n ; i ++ ){
        for(int j = 0 ; j < n ; j ++){
            if(i != j){
                if(g[i][j] == '1') d[i][j] = get_dist(q[i], q[j]);
                else d[i][j] = INF;
            }
        }
    }
    
    floyd();
    
    for(int i = 0 ; i < n ; i ++ ){
        for(int j = 0 ; j < n ; j ++ ){
            if(d[i][j] < INF/ 2){
              maxd[i] = max(maxd[i], d[i][j]) ;  
            }
        }
    }
    
    double res1 = 0;
    for(int i = 0 ; i < n ; i ++ ) res1 = max(res1, maxd[i]);
    double res2 = INF;
    for(int i = 0 ; i < n ; i ++ ){
        for(int j = 0 ; j < n ; j ++ ){
            if(d[i][j] >= INF){
                res2 = min(res2, maxd[i] + get_dist(q[i], q[j]) + maxd[j]);
            }
        }
    }
    printf("%.6lf\n", max(res1, res2));
    return 0;
}

排序

传递闭包

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 26;
int n,m;
bool g[N][N], d[N][N];
bool st[N]; //存储当前变量有没有被输出

void floyd(){
    memcpy(d, g,sizeof d);
    for(int k = 0 ; k < n ; k ++ ){
        for(int i = 0 ; i < n ; i ++ ){
            for(int j = 0 ; j < n ; j ++ ){
                //若i<k,且k<j,则存在i<j
                d[i][j] |= d[i][k] && d[k][j];
            }
        }
    }
}

int check(){
    for(int i = 0; i < n ; i ++ ){
        if(d[i][i])
        //i < i矛盾
            return 2;
    }
    
    for(int i = 0 ; i < n ; i ++ ){
        for(int j = 0 ; j < i ; j ++ ){
            // i与j之间没有关系
            if(!d[i][j] && !d[j][i]) {
                return 0;
            }
        }
    }
    
    return 1;
}

char get_min()
{
    for(int i = 0 ; i < n ; i ++ )
    {
        if(!st[i])
        {
            bool flag = true;
            //找到是否比i小的点
            for(int j = 0 ; j < n ; j ++ ){
                if(!st[j] && d[j][i]){
                    flag = false;
                    break;
                }
            }
            //没有比i小的
            if(flag)
            {
                st[i] = true;
                return  'A' + i;
            }
        }
    }
}
int main()
{
    while(cin >> n >> m , n || m){
        memset(g, 0, sizeof g);
        int type = 0; // 0:不确定 1:唯一确定 2:矛盾
        int t; //每个结果对应的轮数
        for(int i = 1; i <= m ; i ++ ){
            char str[5];
            cin >> str;
            int a = str[0] - 'A', b = str[2] - 'A';
            if(!type){
                g[a][b] = 1;
                floyd();
                type = check();
                if(type) t = i;
            }
        }
        
        if(!type) puts("Sorted sequence cannot be determined.");
        else if(type == 2) printf("Inconsistency found after %d relations.\n", t);
        else{
            memset(st, 0, sizeof st);
            printf("Sorted sequence determined after %d relations: ", t);
            for(int i = 0 ; i < n ; i ++ ) cout << get_min();  //每次输出最小值
            printf(".\n");
        }
    }
    
    return 0;
}

观光之旅

求最小环

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

using namespace std;
const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int d[N][N], g[N][N]; 
int pos[N][N]; //i到j是由哪个点转移的
int path[N]; //存储路径
int cnt;//路径长度

void get_path(int i,int j){
    if(pos[i][j] == 0) return ;
    
    int k = pos[i][j];
    get_path(i, k);
    path[cnt++] = k;
    get_path(k, j);
}

int main()
{
    cin >> n >> m ;
    memset(g, 0x3f, sizeof g);
    for(int i = 0 ; i <= n ; i ++ ) g[i][i] = 0;
    for(int i = 0 ; i < m ; i++ ){
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    int res = INF;
    memcpy(d, g, sizeof d);
    for(int k = 1; k <= n ; k ++ ){
        for(int i = 1; i <= k ; i ++ ){
            for(int j = i + 1 ; j < k ; j ++ ){
                if((long long)d[i][j] + g[i][k] + g[k][j] < res) {
                    res = d[i][j] + g[i][k] + g[k][j];
                    cnt = 0;
                    path[cnt++] = k;
                    path[cnt++] = i;
                    get_path(i, j);
                    path[cnt++] = j;
                }
            }
        }
        
        for(int i = 1 ; i <= n  ; i ++ ){
            for(int j = 1 ; j <= n ; j ++ ){
                if(d[i][j] > d[i][k] + d[k][j]){
                    d[i][j] = d[i][k] + d[k][j];
                    pos[i][j] = k;
                }
            }
        }
    }
    
    if(res > INF / 2) cout << "No solution." << endl;
    else {
        for(int i = 0 ;i < cnt; i ++ ) cout << path[i] << ' ';
    }
    
    return 0;
}

牛站

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

using namespace std;

const int N = 210;

int k, n, m, S, E;
int g[N][N];
int res[N][N];

void mul(int c[][N], int a[][N], int b[][N])
{
    static int temp[N][N];
    memset(temp, 0x3f, sizeof temp);
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                temp[i][j] = min(temp[i][j], a[i][k] + b[k][j]);
    memcpy(c, temp, sizeof temp);
}

void qmi()
{
    memset(res, 0x3f, sizeof res);
    for (int i = 1; i <= n; i ++ ) res[i][i] = 0;

    while (k)
    {
        if (k & 1) mul(res, res, g);    // res = res * g
        mul(g, g, g);   // g = g * g
        k >>= 1;
    }
}

int main()
{
    cin >> k >> m >> S >> E;

    memset(g, 0x3f, sizeof g);
    map<int, int> ids;
    if (!ids.count(S)) ids[S] = ++ n;
    if (!ids.count(E)) ids[E] = ++ n;
    S = ids[S], E = ids[E];

    while (m -- )
    {
        int a, b, c;
        cin >> c >> a >> b;
        if (!ids.count(a)) ids[a] = ++ n;
        if (!ids.count(b)) ids[b] = ++ n;
        a = ids[a], b = ids[b];

        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    qmi();

    cout << res[S][E] << endl;

    return 0;
}

最小生成树

最短网络

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

using namespace std;
const int N = 110,INF = 0x3f3f3f3f;
int n;
int g[N][N];
int d[N];
bool st[N];

int prim()
{
    memset(d, 0x3f, sizeof d);
    int res = 0;
    d[1] = 0;
    for(int i = 0 ; i < n ; i ++ )
    {
        int t = -1;
        for(int j = 1 ; j <= n ; j ++ )
        {
            if(!st[j] &&(t == -1 || d[t] > d[j]))
            {
                t = j;
            }
        }
        
        if(i && d[t] == INF) return INF;
        if(i) res += d[t];
        
        for(int j = 1 ; j <= n ; j ++ ) d[j] = min(d[j], g[t][j]);
        st[t] = true;
    }
    
    return res;
}

int main()
{
    cin >> n ;
    for(int i = 1 ; i <= n ; i ++ )
    {
        for(int j = 1 ; j <= n ; j ++ )
        {
            cin >> g[i][j];
        }
    }
    
    int t = prim();
    cout << t << endl;
    return 0;
}

局域网

最小生成森林:只能用kruscal

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

using namespace std;
const int N = 110,M = 210, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct Edge{
    int a, b, w;
    
    bool operator <(const Edge &W)const {
        return w < W.w;
    }
}edges[M];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m ;
    int sum = 0;
    for(int i = 0 ; i < m ; i ++ )
    {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i].a = a, edges[i].b = b, edges[i].w = w;
        sum += w;
    }
    
    int res = 0, cnt = 0;
    
    sort(edges, edges + m);
    for(int i = 1; i <= n ; i ++ ) p[i] = i;
    for(int i = 0 ; i < m ; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i]. w;
        a = find(a), b = find(b);
        if(a != b)
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }
    
    cout << sum - res << endl;;
    return 0;
}

繁忙的都市

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

using namespace std;
const int N = 310, M = 8010, INF = 0x3f3f3f3f;
int n, m;
int p[N];

struct Edge{
    int a, b, w;
    bool operator<(const Edge &W) const {
        return w < W.w;
    }
}edges[M];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;
    for(int i = 0 ; i < m ; i ++ )
    {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i].a = a, edges[i].b = b, edges[i].w = w;
    }
    
    for(int i = 1 ; i <= n ; i ++ ) p[i] = i;
    sort(edges, edges + m);
    int res = 0, cnt = 0, medge = 0 ;
    
    for(int i = 0 ; i < m ; i ++ )
    {
        int a = edges[i].a ,b = edges[i].b, w = edges[i].w;
        a = find(a), b = find(b);
        if(a != b){
            p[a] = b;
            res += w;
            medge = max(medge, w);
            cnt ++;
        }
    }
    
    cout << cnt << ' ' << medge << endl;
    return 0;
}

联络员

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

using namespace std;
const int N = 2010, M = 10010, INF = 0x3f3f3f3f;
struct Edge
{
    int a, b, w;
    bool operator<(const Edge&W) const {
         return w < W.w;
    }
}edges[M];

int n, m;
int p[N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;
    for(int i = 1 ; i <= n ; i++ ) p[i] = i;
    int res = 0, cnt = 0;
    for(int i = 0 ; i < m ; i ++ )
    {
        int t, a, b, w;
        cin >> t >> a >> b >> w;
        if(t == 1)
        {
            p[find(a)] = find(b);
            cnt ++ ;
            res += w;
        }
        edges[i].a = a;
        edges[i].b = b;
        edges[i].w = w; 
    }
    
    
    sort(edges , edges + m);
    
    for(int i = 0 ; i < m ; i++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        a = find(a), b = find(b);
        if(a != b){
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }
    
    cout << res << endl;
    return 0;
}

连接格点

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

using namespace std;
const int N = 1010, M = 10010, K = N * N * 2, INF = 0x3f3f3f3f ;

struct Edge{
    int a, b, w;
}edges[K];

int n, m, k;
int ids[N][N];
int p[K];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void get_edges()
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}, dw[4] = {1, 2, 1, 2}; //上、右,下,左
    for(int z = 0 ; z < 2 ; z ++ )
        for(int i = 1; i <= n ; i ++ )
            for(int j = 1 ; j <= m ; j ++ )
                for(int u = 0 ; u < 4 ; u ++ )
                    if(u % 2 == z)
                    {
                        int x = i + dx[u], y = j + dy[u], w = dw[u];
                        if(x && x <= n && y && y <= m)
                        {
                            int a = ids[i][j], b = ids[x][y];
                            if(a < b) edges[k ++] = {a, b, w};
                        }
                    }
}

int main()
{
    cin >> n >> m ;
    int t = 1;
    for(int i = 1 ; i <= n ; i ++ ){
        for(int j = 1 ; j <= m ; j ++ )
        {
            ids[i][j] = t++;
        }
    }
    
    for(int i = 1; i <= t ; i ++ ) p[i] = i;
    int x1, y1, x2, y2;
    while(cin >> x1 >> y1 >> x2 >> y2)
    {
        int a = ids[x1][y1], b = ids[x2][y2];
        p[find(a)] = find(b);
    }
    
    get_edges();
    
    int res = 0 ;
    for(int i = 0 ; i < k ;i ++ )
    {
        int a = find(edges[i].a), b = find(edges[i].b), w = edges[i].w;
        if(a != b)
        {
            p[a] = b;
            res += w;
        }
    }
    
    cout << res << endl;
    return 0;
}

最小生成树的扩展应用

新的开始

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

using namespace std;
const int N = 310, INF = 0x3f3f3f3f;

int n;
int d[N];
int g[N][N];
bool st[N];

int prim()
{
    memset(d, 0x3f, sizeof d);
    int res = 0;
    for(int i = 0 ; i < n + 1 ; i ++ )
    {
        int t = -1;
        for(int j = 0; j < n + 1 ; j ++ )
            if(!st[j] && (t == -1 || d[j] < d[t]))
                t = j;
                
        if(i && d[t] == INF) return INF;
        if(i) res += d[t];
        for(int j = 0 ; j < n + 1; j ++ ) d[j] = min(d[j], g[t][j]);
        st[t] = true;
    }
    
    return res;
}

int main()
{
    cin >> n ;
    memset(g, 0x3f, sizeof g);
    for(int i = 1; i <= n ; i++ )
    {
        int a;
        cin >> a;
        g[0][i] = g[i][0] = a;
    }
    
    for(int i = 1; i <= n ; i++ )
        for(int j = 1 ; j <= n; j++ )
            cin >> g[i][j];
            
    int t = prim();
    cout << t << endl;
    return 0;
}

北极通讯网络

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

#define x first
#define y second

using namespace std;
typedef pair<int,int> PII;
const int N = 510 , M = N * N / 2;

int n,m,k;
PII q[N];
int p[N];
struct Edge{
    int a, b;
    double w;
    bool operator <(const Edge& W) const {
        return w < W.w;
    }
}e[M];

double get_dist(PII a, PII b)
{
    int dx = a.x - b.x;
    int dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

int find(int x)
{
    if(p[x] != x) return p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> k;
    for(int i = 0 ; i < n ; i++) cin >> q[i].x >> q[i].y;
    for(int i = 0 ; i < n ; i ++ )
        for(int j = 0 ; j < i ; j ++) 
            e[m ++ ] = {i , j , get_dist(q[i], q[j])};
            
    sort(e, e + m);
    for(int i = 0 ; i < n ; i ++ ) p[i] = i;
    int cnt = n;
    double res = 0;
    for(int i = 0 ; i < m ; i ++ )
    {
        if(cnt <= k ) break;
        int a = find(e[i].a) , b = find(e[i].b);
        double w = e[i].w;
        
        if(a != b)
        {
            p[a] = b;
            cnt -- ;
            res = w;
        }
    }
    
    printf("%.2lf\n", res);
    return 0;
}

走廊泼水节

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

using namespace std;
const int N = 6010;

struct Edge{
    int a, b, w;
    bool operator <(const Edge &W) const {
        return w < W.w;
    }
}e[N];

int n;
int p[N];
int sz[N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    int T;
    cin >> T;
    while(T -- )
    {
        cin >> n;
        for(int i = 0 ; i < n - 1 ; i ++ )
        {
            int a, b, w;
            cin >> a >> b >> w;
            e[i] = {a, b, w};
        }
        
        sort(e, e + n - 1);
        for(int i = 1 ; i <= n ; i ++ ) p[i] = i, sz[i] = 1;
        
        int res = 0;
        for(int i = 0 ; i < n - 1 ; i ++ )
        {
            int a = find(e[i].a), b = find(e[i].b);
            int w = e[i].w;
            if(a != b)
            {
                res += (sz[a] * sz[b] - 1) * (w + 1);
                sz[b] += sz[a];
                p[a] = b;
                
            }
        }
        
        cout << res << endl;
    }
    return 0;
}

秘密的牛奶运输(未做)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 505, M = 10005, L = 10, INF = 2e9;
int n, m, fa[N][L], fMax[N][L][2], f[N];
int dep[N];
LL res = 0, ans = 9e18;
bool st[M];
/*
fMax[i][j][0 / 1] 表示 i 这个点往上跳 2 ^ j 个点,这个范围内的 最大值 / 最小值。
*/
int head[N], numE = 0;
struct Edge{
    int next, from, to, dis;
}e[M << 1];
struct E{
    int u, v, w;
    bool operator < (const E &x) const{
        return w < x.w;
    }
}g[M];
void inline addEdge(int from, int to, int dis){
    e[++numE].next = head[from];
    e[numE].to = to;
    e[numE].from = from;
    e[numE].dis = dis;
    head[from] = numE;
}

void dfs(int u, int last){
    for(int i = 1; fa[fa[u][i - 1]][i - 1]; i++){
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
        if(fMax[u][i - 1][0] == fMax[fa[u][i - 1]][i - 1][0]){
            fMax[u][i][0] = fMax[u][i - 1][0];
            fMax[u][i][1] = max(fMax[u][i - 1][1], fMax[fa[u][i - 1]][i - 1][1]);
        }else{
            fMax[u][i][0] = max(fMax[u][i - 1][0], fMax[fa[u][i - 1]][i - 1][0]);
            fMax[u][i][1] = min(fMax[u][i - 1][0], fMax[fa[u][i - 1]][i - 1][0]);
        }
    }
    for(int i = head[u]; i; i = e[i].next){
        int v = e[i].to;
        if(v == last) continue;
        fa[v][0] = u, fMax[v][0][0] = e[i].dis;
        dep[v] = dep[u] + 1, fMax[v][0][1] = -INF;
        dfs(v, u);
    }
}
int find(int x){
    return x == f[x] ? x : f[x] = find(f[x]);
}

void inline update(int v, int &val1, int &val2){
    if(v > val1) val2 = val1, val1 = v;
    else if(v > val2 && v != val1) val2 = v;
}


void inline lca(int x, int y, int &val1, int &val2){
    if(dep[x] < dep[y]) swap(x, y);
    for(int i = L - 1; ~i; i--)
        if(dep[x] - (1 << i) >= dep[y]) {
            update(fMax[x][i][0], val1, val2);
            update(fMax[x][i][1], val1, val2);
            x = fa[x][i];
        }
    if(x == y) return;
    for(int i = L - 1; ~i; i--){
        if(fa[x][i] != fa[y][i]){
            update(fMax[x][i][0], val1, val2);
            update(fMax[x][i][1], val1, val2);
            update(fMax[y][i][0], val1, val2);
            update(fMax[y][i][1], val1, val2);
            x = fa[x][i], y = fa[y][i];
        }
    }
    update(fMax[x][0][0], val1, val2);
    update(fMax[x][0][1], val1, val2);
    update(fMax[y][0][0], val1, val2);
    update(fMax[y][0][1], val1, val2);
}

int main(){
    scanf("%d%d", &n, &m);

    for(int i = 1; i <= n; i++)
        f[i] = i;

    for(int i = 1; i <= m; i++){
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        g[i] = (E){ u, v, w };
    }
    sort(g + 1, g + 1 + m);
    for(int i = 1; i <= m; i++){
        int u = find(g[i].u), v = find(g[i].v), w = g[i].w;
        if(u != v){
            f[u] = v, res += w, st[i] = true;
            addEdge(g[i].u, g[i].v, w); addEdge(g[i].v, g[i].u, w);
        }
    }
    dfs(1, 0);
    for(int i = 1; i <= m; i++){
        if(!st[i]){
            int val1 = -INF, val2 = -INF;

            lca(g[i].u, g[i].v, val1, val2);
            if(val1 == g[i].w)
                ans = min(ans, res - val2 + g[i].w);
            else ans = min(ans, res - val1 + g[i].w);
        }
    }
    printf("%lld\n", ans);
    return 0;
}

文章作者: wck
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 wck !
评论
  目录