目录
最短路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,最后就剩