保研机试模板整理

这篇博客详细总结了图论中的算法,包括最短路算法(SPFA、Dijkstra、Floyd),博弈论(SG函数、尼姆博弈、威佐夫博弈等),以及数据结构的应用,如Tarjan算法、最小生成树和字典树等。还探讨了路径最大权值最小问题和多线程动态规划问题,适合进阶学习者参考。
摘要由CSDN通过智能技术生成

目录

 

最短路spfa、dij、floyd + 记录路径

floyd:

spfa 和 dij

次短路

其他例题:

博弈

SG函数

尼姆博弈

威佐夫博弈

巴什博弈

 

Tarjan算法

缩点裸题

强联通缩点的应用

最小生成树

prime

Krusal

匈牙利裸题

区间更新区间查询

DP

求最大子矩阵

最大子段和

最长公共子序列

最长公共子串

LIS

LICS

矩阵取数(多线程DP)

背包模板

并查集

字符串

马拉车

kmp

对主串做next数组

对子串做next数组

扩展KMP

字典树

矩阵快速幂

错排公式

康拓展开式

逆元

线性筛

LCA


最短路spfa、dij、floyd + 记录路径

floyd:

思路:用一个二维数组path[][]来记录。这里有两种方法,1 用path[ i ][ j ]记录 j 的前驱顶点 ;2 用path[ i ][ j ]记录 i 的后面的点。

提醒:需要注意的是path的初始化

#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
#define INF 1000000+10
using namespace std;
int Map[500][500]; 
int pre[500][500];//记录当前顶点的 前一个顶点 
int n, m;
void init()
{
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			Map[i][j] = i==j ? 0 : INF;
			pre[i][j] = j;//初始化 
		}
	}
} 
void getMap()
{
	int a, b, c;
	while(m--)
	{
		scanf("%d%d%d", &a, &b, &c);
		if(Map[a][b] > c)
		Map[a][b] = c;
	} 
}
void floyd()
{
	int i, j, k;
	for(k = 1; k <= n; k++)
	{
		for(i = 1; i <= n; i++)
		{
			for(j = 1; j <= n; j++)
			{
				if(Map[i][j] > Map[i][k] + Map[k][j])
				{
					Map[i][j] = Map[i][k] + Map[k][j];
					pre[i][j] = pre[i][k];//记录 
				}
			} 
		} 
	} 
}
int main()
{
	int s, e;
	while(scanf("%d %d", &n, &m), n||m)
	{
		init();
		getMap();
		floyd();
		while(scanf("%d%d", &s, &e), s!=-1||e!=-1)
		{
			if(s == e)
			{
				printf("从%d到%d的最优路线 : %d\n", s, e, s);
				printf("最小花费 : %d\n", 0);
				continue;
			}
			printf("从%d到%d的最优路线 : %d", s, e, s);
			int now = pre[s][e];
			while(1)
			{
				printf("-->%d", now);
				if(now == e)
				break;
				now = pre[now][e];
			}
			printf("\n");
			printf("最小花费 : %d\n", Map[s][e]);
		}
	}
	return 0;
}
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			Map[i][j] = i==j ? 0 : INF;
			pre[i][j] = j;//初始化 
		}
	}
} 
void getMap()
{
	int a, b, c;
	while(m--)
	{
		scanf("%d%d%d", &a, &b, &c);
		if(Map[a][b] > c)
		Map[a][b] = c;
	} 
}
void floyd()
{
	int i, j, k;
	for(k = 1; k <= n; k++)
	{
		for(i = 1; i <= n; i++)
		{
			for(j = 1; j <= n; j++)
			{
				if(Map[i][j] > Map[i][k] + Map[k][j])
				{
					Map[i][j] = Map[i][k] + Map[k][j];
					pre[i][j] = pre[i][k];//记录 
				}
			} 
		} 
	} 
}
int main()
{
	int s, e;
	while(scanf("%d %d", &n, &m), n||m)
	{
		init();
		getMap();
		floyd();
		while(scanf("%d%d", &s, &e), s!=-1||e!=-1)
		{
			if(s == e)
			{
				printf("从%d到%d的最优路线 : %d\n", s, e, s);
				printf("最小花费 : %d\n", 0);
				continue;
			}
			printf("从%d到%d的最优路线 : %d", s, e, s);
			int now = pre[s][e];
			while(1)
			{
				printf("-->%d", now);
				if(now == e)
				break;
				now = pre[now][e];
			}
			printf("\n");
			printf("最小花费 : %d\n", Map[s][e]);
		}
	}
	return 0;
}

spfa 和 dij

HDU 1224

题意:给你n个城市,每个城市都有一个风景值,再给你m条路,每条路连接两个城市,只能从序号小的到序号大的,问你从1号到n+1号能经过的风景值得和最大为多少,并且输出路径。

import java.util.Arrays;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
import java.util.Vector;

class Main
{
	static final int maxn = 1000;
	static final int INF = 0x3f3f3f3f; 
	static int[] dis = new int[maxn];
	static int[] book = new int[maxn];
	static int[] pre = new int[maxn];
	static int[] cost = new int[maxn];
	static int[] res = new int[maxn];
	static class node implements Comparable<node>
	{
		int to, w;
		node(int tt, int ww)
		{
			this.to = tt;
			this.w = ww;
		}
		@Override
		public int compareTo(node o) 
		{
			// TODO Auto-generated method stub
			return this.w - o.w;
		}
	}
	static Vector<node>[] v = new Vector[maxn];
	static void spfa()
	{
		Arrays.fill(book, 0);
		Arrays.fill(pre, -1);
		Arrays.fill(dis, 0);
		Queue<Integer> q = new LinkedList<Integer>();
		q.add(1);
		dis[1] = 0;
		while(!q.isEmpty())
		{
			int u = q.poll();
			book[u] = 0;
			for(int i = 0; i < v[u].size(); i++)
			{
				int to = v[u].get(i).to;
				if(dis[to] < dis[u] + cost[to])
				{
					dis[to] = dis[u] + cost[to];
					pre[to] = u;
					if(book[to] == 0)
					{
						book[to] = 1;
						q.add(to);
					}
				}
			}
		}
	}
	static void dij()
	{
		Arrays.fill(dis, 0);
		Arrays.fill(pre, -1);
		Queue<node> pq = new PriorityQueue<node>();
		pq.add(new node(1, 0));
		while(!pq.isEmpty())
		{
			int u = pq.poll().to;
			for(int i = 0; i < v[u].size(); i++)
			{
				int to = v[u].get(i).to;
				if(dis[to] < dis[u] + cost[to])
				{
					pre[to] = u;
					dis[to] = dis[u] + cost[to];
					pq.add(new node(to, dis[to]));
				}
			}
		}
	}
	public static void main(String[] args) 
	{
		Scanner sc = new Scanner(System.in);
		for(int i = 0; i < maxn; i++) 
			v[i] = new Vector<node>();
		int _, n, m, ca = 1;
		_ = sc.nextInt();
		while(_-- > 0)
		{
			for(int i = 0; i < maxn; i++)
			{
				v[i].clear();
				cost[i] = 0;
				
			}
			n = sc.nextInt();
			for(int i = 1; i <= n; i++)
				cost[i] = sc.nextInt();
			m = sc.nextInt();
			for(int i = 1; i <= m; i++)
			{
				int u = sc.nextInt();
				int to = sc.nextInt();
				if(u > to) 
				{
					int tmp = u;
					u = to;
					to = tmp;
				}
				v[u].add(new node(to, 0));
			}
			//spfa();
			dij();
		    System.out.println("CASE " + (ca++) + "#");  
            System.out.println("points : " + dis[n + 1]);  
            System.out.print("circuit : ");  
			int cnt = 0;
			int p = n + 1;
			while(p != -1)
			{
				res[cnt++] = p;
				p = pre[p];
			}
			for(int i = cnt-1; i > 0; i--)
				System.out.print(res[i] + "->");
			System.out.println(1);
			if(_ != 0)
				System.out.println();
		}
	}
}

次短路

 

题意:给你一个有向图,问你他的次短路长度(与最短路至少有一条边不同即可)

思路:如果最短路有多条,那答案就是最短路,否则就是次短路

次短路思路: 
        把求最短路时更新最短路的那部分改一下。 
        dis1,dis2数组分别记录到该点的最短路和次短路 
        分三种情况: 
            1.若该点最短路+下一条边比到下个点的最短路短,则更新下个点的最短路,同时更新次短路为原最短路 
            2.若该点次短路+下一条边比到下个点的次短路短,则更新下个点的次短路 
            3.若该点最短路+下一条边比到下个点的最短路长同时比下个点的次短路短,则更新下个点的次短路 

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, head[maxn];
ll cnt[maxn];
ll dis1[maxn], dis2[maxn], dis[maxn];
bool book[maxn];
 
struct node
{
    int v, w, next;
}edge[maxn];
 
void addEdge(int u, int v, int w)
{
    edge[k].v = v;
    edge[k].w = w;
    edge[k].next = head[u];
    head[u] = k++;
}
 
void spfa(int u)
{
    for(int i = 1; i <= n; i++) dis1[i] = INF;
    for(int i = 1; i <= n; i++) dis2[i] = INF;
    memset(book, 0, sizeof(book));
    queue<int> q;
    q.push(u);
    dis1[u] = 0;
    book[u] = 1;
    while(!q.empty())
    {
        u = q.front(); q.pop();
        book[u] = 0;
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].v;
            int w = edge[i].w;
            if(dis1[v] > dis1[u]+w)
            {
                dis2[v] = dis1[v];
                dis1[v] = dis1[u]+w;
                if(!book[v]) book[v] = 1, q.push(v);
            }
            if(dis2[v] > dis2[u]+w)
            {
                dis2[v] = dis2[u]+w;
                if(!book[v]) book[v] = 1, q.push(v);
            }
            if(dis1[v] < dis1[u]+w && dis2[v] > dis1[u]+w)
            {
                dis2[v] = dis1[u]+w;
                if(!book[v]) book[v] = 1, q.push(v);
            }
        }
    }
}
 
void spfa2(int u)
{
    for(int i = 1; i <= n; i++) dis[i] = INF;
    memset(book, 0, sizeof(book));
    queue<int> q;
    q.push(u);
    book[u] = cnt[u] = 1;
    dis[u] = 0;
    while(!q.empty())
    {
        u = q.front(); q.pop();
        book[u] = 0;
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].v;
            int w = edge[i].w;
            if(dis[u]+w < dis[v])
            {
                dis[v] = dis[u]+w;
                if(!book[v]) book[v] = 1, q.push(v);
                cnt[v] = cnt[u];
            }
            else if(dis[u]+w == dis[v])
            {
                cnt[v] += cnt[u];
            }
        }
    }
}
 
int main(void)
{
    int t;
    cin >> t;
    while(t--)
    {
        k = 0;
        memset(cnt, 0, sizeof(cnt));
        memset(head, -1, sizeof(head));
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i++)
        {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            addEdge(u, v, w);
            addEdge(v, u, w);
        }
        spfa(1);
        spfa2(1);
        if(cnt[n] > 1) printf("%lld\n", dis1[n]);
        else printf("%lld\n", dis2[n]);
    }
    return 0;
}

 

其他例题:

 

路径最大权值最小

博弈

SG函数

sg 即Graph Game,把博弈游戏抽象成有向无环图
(1) 有向无环图
(2) 玩家1先移动,起点是x0
(3) 两个玩家轮流移动
(4) 对于顶点x, 玩家能够移动到的顶点集记为F(x).
(5) 不能移动的玩家会输掉游戏
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、 mex{2,3,5}=0、mex{}=0。
定义: 一个图的Sprague-Grundy函数(X,F)是定义在X上的非负函数g(x),并且满足:

       g(x) = mex{g(y) : y∈F(x)}

假设游戏 Gi的SG函数是gi, i=1,…,n, 则G = G1 + … + Gn 的 SG函数是g(x1,…,xn) = g1(x1)^…^gn(xn).

g(x) > 0 必胜, g(x) == 0 必输

例题:

  这是一个二人游戏,一共有3堆石子,数量分别是m, n, p个,两人轮流走, 每走一步可以选择任意一堆石子,然后取走f个, f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量), 最先取光所有石子的人为胜者,假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢,  如果先手的人能赢,请输出“Fibo”,否则请输出“Nacci”,每个实例的输出占一行。

#include <iostream>  
#include <algorithm>  
#include <cstring>  
#include <cstdio>  
using namespace std;  
const int maxn = 1e3 + 5;  
int f[maxn], sg[maxn], book[maxn];  
void init()  
{  
    f[1] = 1;  
    f[2] = 2;  
    for(int i = 3; i <= maxn; i++)  
    f[i] = f[i-1] + f[i-2];  
}  
void get_sg()  
{  
    for(int i = 1; i <= maxn; i++)  //从1枚举所有状态
    {  
        memset(book, 0, sizeof(book));  //计算mex的
        for(int j = 1; f[j] <= i; j++)   //枚举这个状态所有可能到达的状态
        {  
            book[sg[i-f[j]]] = 1;   // 计算能到达的状态的sg是否出现过
        }  
        for(int j = 0; book[j]; j++)   //计算mex
            sg[i] = j + 1;  
    }  
}  
int main()  
{  
    int m, n, p;  
    init();  
    get_sg();  
    while(cin >> m >> n >> p, m+n+p)  
    {  
        puts(sg[n]^sg[m]^sg[p] ? "Fibo" : "Nacci");  
    }  
    return 0;  
}  

尼姆博弈

尼姆博奕 先取走赢跟先取走输 都是抑或和 = 0 输了 只是先取走输要特判都是1的时候

#include <cstdio>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
using namespace std;  
int main()  
{  
    int t, n;  
    scanf("%d" ,&t);  
    while(t--)  
    {  
        scanf("%d", &n);  
        int a, ans = 0, flag = 0;  
        for(int i = 1; i <= n; i++)  
        {  
            scanf("%d", &a);  
            ans ^= a;  
            if(a > 1)  //特判都是1 的时候  
               flag = 1;  
        }  
  
        if(!flag)  
        {  
            if(n%2) printf("Brother\n");  
            else printf("John\n");  
            continue;  
        }  
        if(ans == 0)  
            printf("Brother\n");  
        else  
            printf("John\n");  

威佐夫博弈

题目:有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。如果你胜,你第1次怎样取子? 

思路:威佐夫博弈,遇到奇异局势则必败第k个奇异局势(a(k), b(k)),a(k)是前面没有出现过的最小自然数(a(k)=(int)(k*(sqrt(5.0)+1)/2),b(k)=a(k)+k,采用适当的方法,这里不再证明,接下来只要判断就行了

同时从两堆中取相同数量的石头或者从一堆中取一定数量的石头,可以将非奇异局势变为奇异局势

#include <cstdio>  
#include <cmath>  
#include <algorithm>  
using namespace std;  
  
double g = (sqrt(5.0) + 1) / 2;  // 黄金分割数1.618...  
  
int main()  
{  
    int a, b;  
    while(scanf("%d %d", &a, &b) == 2) {  
        if(a == 0 && b == 0)  
            break;  
  
        int k = b - a;  
        if(a == (int)(g * k))    // 只要判断a即可,因为b=a+k是恒成立的  
        {  
            printf("0\n");  
        }  
        else  
        {  
            printf("1\n");  
            // 将非奇异局势变为奇异局势  
            for(int i=1; i<=a; i++) // 同时从两堆中取相同数量的石头,注意这里是从1到a枚举  
            {  
                int x = a - i, y = b - i;  
                int k = y - x;  
                if(x == (int)(g * k))  
                    printf("%d %d\n", x, y);  
            }  
            for(int i=b-1; i>=0; i--)   // 从一堆中取一定数量的石头,这里是从b-1往下枚举到0  
            {  
                int x = a, y = i;  
                if(x > y)  
                    swap(x, y);  
  
                int k = y - x;  
                if(x == (int)(g * k))  
                    printf("%d %d\n", x, y);  
            }  
        }  
    }  
    return 0;  
}  

巴什博弈

有三个数字n,p,q,表示一堆硬币一共有n枚,从这个硬币堆里取硬币,一次最少取p枚,最多q枚,如果剩下少于p枚就要一次取完。两人轮流取,直到堆里的硬币取完,最后一次取硬币的算输。对于每一行的三个数字,给出先取的人是否有必胜策略,如果有回答WIN,否则回答LOST

思路:
可以分成三种情况。
(1)如果n%(p+q)==0,那么A必胜。取胜步骤如下:
A第一次取q,接下去B取m,A就取p+q-m,那么最后剩下的就是p个硬币留给B取,B必败。
(2)如果n=(p+q)*k+s,(1<=s<=p),那么B必胜。取胜步骤如下:
A取一个m,那么B就取p+q-m,那么最后剩下的就是s个银币留给A取,A必败。
(3)如果n=(p+q)*k+s,(p<s<=q),那么A必胜。取胜步骤如下:
A第一次取一个t,(1<= s-t <=p),那么B取一个m,A取p+q-m,最后就剩

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值