专题四 最短路练习

专题四 最短路练习

POJ 2387 Til the Cows Come Home

题意: 给你n个点,给出a到b的距离,a,b边是可以互想抵达的,求1到n的最短距离。实质:n个顶点m条边的无向图,求1到n的最短路径。

题解: 模板题

代码:

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

using namespace std;

typedef pair<int, int> PII;
int const N = 2e5 + 10;
int e[N], ne[N],w[N], h[N], idx, n, m, dis[N], st[N];

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

// 堆优化版dijksyta
int dijkstra() {
    memset(dis, 0x3f, sizeof dis);  // 初始化距离为无穷
    priority_queue<PII, vector<PII>, greater<PII> > q;  // 定义一个按照距离从小到大排序的优先队列,第一维:距离,第二维:点
    dis[1] = 0;  // 一开始源点距离为0
    q.push({0, 1});  // 把源点信息放入队列
    while (q.size()) {  // 每个点只出入队列一次
        PII t = q.top();
        q.pop();
        
        int distance = t.first, ver = t.second;  // 最小距离和相对应的点
        if (st[ver]) continue;  // 这个操作保证每个点只出入队一次,因为队列里面可能会出现{dis1[3], 3}, {dis2[3], 3}的情况,这样保证dis1[3]<dis2[3]时,3号点只进出入队一次
        st[ver] = 1;  // 标记,因为dijkstra的贪心策略保证每个点只需要进出队一次
        
        for (int i = h[ver]; ~i; i = ne[i]) {  // 遍历ver的邻接点
            int j = e[i];
            if (dis[j] > distance + w[i]) {
                dis[j] = distance + w[i];
                q.push({dis[j], j});  // 这里不需要判断st,因为一旦更新发现更小必须放入队列
            }
        }
    }
    return dis[n] != 0x3f3f3f3f? dis[n]: -1;
}

int main() {
    cin >> m >> n;
    memset(h, -1, sizeof h);  
    for (int i = 1, a, b, c; i <= m; ++i) {  // 读入m条边
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    cout << dijkstra();
    return 0;
}

POJ 2253 Frogger

题意:

题解: floyd最短路模板题

代码:

POJ 1797 Heavy Transportation

题意: N个点,M条边,每条边有权值。求一条1号点到N号点的路径,要求使得路径中的边权最小值最大,打印这个最小值。(1 <= n <= 1000、1 <= u,v <= n , 0< w <= 1e6)

题解: 魔改dijkstra。原先dis[i]维护i点到源点的最短路,且使用的操作是做加法。现在dis[i]维护i点到源点路径上的最小值,使用的操作是做min。然后优先队列需要改为大根堆,初始化源点需要改为正无穷,其他点为0.

代码:

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

using namespace std;

typedef pair<int, int> PII;
int const N = 1e3 + 10, M = N * N / 2;
int e[M], ne[M], w[M], h[N], idx, n, m, dis[N], st[N], T, kase = 1;

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

// 堆优化版dijksyta
int dijkstra() {
    for (int i = 1; i <= n; ++i) dis[i] = 0, st[i] = 0;
    priority_queue<PII>
        q;  // 定义一个按照距离从小到大排序的优先队列,第一维:距离,第二维:点
    dis[1] = 0x3f3f3f3f;            // 一开始源点距离为0
    q.push({0x3f3f3f3f, 1});  // 把源点信息放入队列
    while (q.size()) {        // 每个点只出入队列一次
        PII t = q.top();
        q.pop();

        int distance = t.first, ver = t.second;  // 最小距离和相对应的点
        if (st[ver])
            continue;  // 这个操作保证每个点只出入队一次,因为队列里面可能会出现{dis1[3],
                       // 3}, {dis2[3],
                       // 3}的情况,这样保证dis1[3]<dis2[3]时,3号点只进出入队一次
        st[ver] = 1;  // 标记,因为dijkstra的贪心策略保证每个点只需要进出队一次

        for (int i = h[ver]; ~i; i = ne[i]) {  // 遍历ver的邻接点
            int j = e[i];
            if (dis[j] < min(distance, w[i])) {
                dis[j] = min(distance, w[i]);
                q.push(
                    {dis[j],
                     j});  // 这里不需要判断st,因为一旦更新发现更小必须放入队列
            }
        }
    }
    return dis[n];
}

int main() {
    cin >> T;
    while (T--) {
        scanf("%d%d", &n, &m);
        idx = 0;
        for (int i = 1; i <= n; ++i) h[i] = -1;
        for (int i = 1, a, b, c; i <= m; ++i) {  // 读入m条边
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c), add(b, a, c);
        }
        printf("Scenario #%d:\n", kase++);
        printf("%d\n\n", dijkstra());
    }

    return 0;
}

POJ 3268 Silver Cow Party

题意: 余欣妍在他的农场里养了n头牛,每头牛都有一个固定的位置,每天这些牛都要到余欣妍家里参加聚会然后回去,给出n,m,代表牛数+1和有向边数,接着是m条有向边(a,b,c),代表从牛a到牛b需要花费c秒,给你余欣妍家的位置X(1~n的除x外的编号为牛的位置),每头牛都有一个参加聚会并且回到原来位置的最短时间,从这些最短时间里找出一个最大值输出 N<=1000,M<=100,000,c<=100

题解: 要求出每个点到x,然后x到每个点,我只要正着做一遍最短路,就知道x到每个点的最短路;然后建立反图,再跑一边最短路,就可以知道每个点到x的最短路。然后枚举每个点,计算正向的dis和反向的dis累加取max即可。

代码:

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

using namespace std;

// #define int long long
typedef long long LL;
typedef pair<LL, int> PII;
int const N = 1e3 + 10, M = 1e5 + 10;
int e[M], ne[M], w[M], h[N], idx, n, m;
LL dis1[N], dis2[N];
int st[N], x;
struct Edge {
    int u, v, w;
} edge[M];

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

// 堆优化版dijksyta
void dijkstra(LL dis[]) {
    for (int i = 1; i <= n; ++i) dis[i] = 0x3f3f3f3f3f3f3f3f, st[i] = 0;
    priority_queue<PII, vector<PII>, greater<PII> >
        q;  // 定义一个按照距离从小到大排序的优先队列,第一维:距离,第二维:点
    dis[x] = 0;         // 一开始源点距离为0
    q.push({(LL)0, x});     // 把源点信息放入队列
    while (q.size()) {  // 每个点只出入队列一次
        PII t = q.top();
        q.pop();

        LL distance = t.first;
        int ver = t.second;  // 最小距离和相对应的点
        if (st[ver])
            continue;  // 这个操作保证每个点只出入队一次,因为队列里面可能会出现{dis1[3],
                       // 3}, {dis2[3],
                       // 3}的情况,这样保证dis1[3]<dis2[3]时,3号点只进出入队一次
        st[ver] = 1;  // 标记,因为dijkstra的贪心策略保证每个点只需要进出队一次

        for (int i = h[ver]; ~i; i = ne[i]) {  // 遍历ver的邻接点
            int j = e[i];
            if (dis[j] > distance + w[i]) {
                dis[j] = distance + w[i];
                q.push(
                    {dis[j],
                     j});  // 这里不需要判断st,因为一旦更新发现更小必须放入队列
            }
        }
    }
}

int main() {
    cin >> n >> m >> x;
    // 正向建边
    memset(h, -1, sizeof h);
    for (int i = 1, a, b, c; i <= m; ++i) {  // 读入m条边
        scanf("%d%d%d", &a, &b, &c);
        edge[i] = {b, a, c};
        add(a, b, c);
    }
    dijkstra(dis1);
    idx = 0;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= m; ++i) add(edge[i].u, edge[i].v, edge[i].w);
    dijkstra(dis2);
    LL res = 0;
    for (int i = 1; i <= n; ++i) {
        if (dis1[i] != 0x3f3f3f3f && dis2[i] != 0x3f3f3f3f)
            res = max(res, dis1[i] + dis2[i]);
    }
    cout << res;
    return 0;
}

POJ 1860 Currency Exchange

题意: 我们的城市有几个货币兑换点。让我们假设每一个点都只能兑换专门的两种货币。可以有几个点,专门从事相同货币兑换。每个点都有自己的汇率,外汇汇率的A到B是B的数量你1A。同时各交换点有一些佣金,你要为你的交换操作的总和。在来源货币中总是收取佣金。 例如,如果你想换100美元到俄罗斯卢布兑换点,那里的汇率是29.75,而佣金是0.39,你会得到(100 - 0.39)×29.75=2963.3975卢布。 你肯定知道在我们的城市里你可以处理不同的货币。让每一种货币都用唯一的一个小于N的整数表示。然后每个交换点,可以用6个整数表描述:整数a和b表示两种货币,a到b的汇率,a到b的佣金,b到a的汇率,b到a的佣金。 nick有一些钱在货币S,他希望能通过一些操作(在不同的兑换点兑换),增加他的资本。当然,他想在最后手中的钱仍然是S。帮他解答这个难题,看他能不能完成这个愿望。

题解: 每种货币代表一个点,给货币兑换的关系看作两点的边建图,跑一遍SPFA判正环,若存在正环则必有解使得最终货币价值上升,为什么,因为按照正环兑换一遍价值就会上升,那么一定可以通过有限次正环使得价值大到回到起点的时候价值依然大于初始值;

代码:

有点不想写,贴上大佬代码:https://blog.csdn.net/weixin_43209425/article/details/104947933

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
 
const int N = 100+10;
const int INF = 0X3F3F3F3F3F;
struct node{
	int to,nxt;
	double rate,com;
	node(){}
	node(int _to,int _nxt,double _rate,double _com):to(_to),nxt(_nxt),rate(_rate),com(_com){}
}e[N<<1];
int head[N],tot;
 
void add(int u,int v,double r,double c){
	e[tot]=node(v,head[u],r,c);
	head[u]=tot++;
}
 
int n,m,vis[N],out[N],S;
double dis[N],w;
 
bool SPFA(){
	for(int i=1;i<=n;i++) dis[i]=-INF,vis[i]=out[i]=0;
	vis[S]=1,out[S]=1,dis[S]=w; 
	queue<int>que;
	que.push(S);
	while(!que.empty()){
		int u=que.front(); que.pop(); vis[u]=0;
		for(int i=head[u];i!=-1;i=e[i].nxt){
			int v=e[i].to;
		    double r=e[i].rate,c=e[i].com;
		    if(dis[v]<(dis[u]-c)*r){
		    	dis[v]=(dis[u]-c)*r;
		    	if(!vis[v]){
		    		vis[v]=1;
		    		que.push(v);
		    		if(++out[v]>n) return true;
				}
			}
			if(dis[S]>w) return true;
		}
	}
	return false;
} 
 
int main()
{
	memset(head,-1,sizeof(head)),tot=0;
	scanf("%d%d%d%lf",&n,&m,&S,&w);
	for(int i=0;i<m;i++){
		int u,v;
		double r1,c1,r2,c2;
		scanf("%d%d%lf%lf%lf%lf",&u,&v,&r1,&c1,&r2,&c2);
		add(u,v,r1,c1);
		add(v,u,r2,c2);
	}
	if(SPFA()) printf("YES");
	else printf("NO"); 
}

POJ 3259 Wormholes

题意: 教学楼里有很多教室,这些教室由双向走廊连接。另外,还存在一些单向的秘密通道,通过它们可以回到过去。现在有 N (1 ≤ N ≤ 500) 个教室,编号 1…N, M (1 ≤ M ≤ 2500) 条走廊,和 W (1 ≤ W ≤ 200) 条秘密通道。

DY在养猫之余,还是一个时间旅行爱好者。她希望从一间教室出发,经过一些走廊和秘密通道,回到她出发之前的某个时间。

共有F (1 ≤ F ≤ 5) 组数据。对每组数据,判断DY是否有回到过去的可能性。不存在耗时超过10,000秒的走廊,且不存在能带DY回到10,000秒之前的秘密通道。

题解: 判断是否存在负环

代码:

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

using namespace std;

int const N = 5e2 + 10, M = 3e6 + 10;
int e[M], ne[M], w[M], idx, h[N], t, n, m, wi, dist[N], cnt[N], st[N];

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

// spfa求负环(正环)
bool spfa() {
    queue<int> q;
    
    memset(dist, 0x3f, sizeof dist);
    memset(cnt, 0, sizeof cnt);
    memset(st, 0, sizeof st);
    // 思路1:在原图的基础上新建一个虚拟源点,从该点向其他所有点连一条权值为0的有向边。那么原图有负环等价于新图有负环。此时在新图上做spfa,将虚拟源点加入队列中。然后进行spfa的第一次迭代,这时会将所有点的距离更新并将所有点插入队列中。执行到这一步,就等价于把全部点放入队列。这样cnt维护的就是到虚拟源点的距离。
    // 思路2:spfa算法一开始加入多少个点到队列都没有关系,因为这些点一开始距离都是无穷,不会引起答案差异。但是由于判断负环,必须把所有点都加入,防止出现孤立连通块的情况。这样cnt维护的就是和某些虚拟点的距离。
    for (int i = 1; i <= n; i ++ ) {
        st[i] = true;
        q.push(i);
    }
    // dist[0] = 0, st[0] = 1, q.push(0);  如果希望能够正确求出dis数组,那么还需要加上这个代码
    while (q.size())  {
        int t = q.front();  // 取队首
        q.pop();  // 出队首

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[t] + w[i]) { // 这里是判断负环,如果是判正环:1.初始化写成memset(dis, 0xc0, sizeof dis), 2.更新条件写成dist[j] < dist[t] + w[i]
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;  // 更新边数
                if (cnt[j] >= n) return true;  // 如果j点到源点的边数大于等于n
                if (!st[j]) {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

int main() {
    cin >> t;
    while (t--) {
        cin >> n >> m >> wi;
        idx = 0;
        memset(h, -1, sizeof h);
        for (int i = 1, a, b, c; i <= m; ++i) {
            scanf("%d %d %d", &a, &b, &c);
            add(a, b, c), add(b, a, c);
        }
        for (int i = 1, a, b, c; i <= wi; ++i) {
            scanf("%d %d %d", &a, &b, &c);
            add(a, b, -c);
        }
        
        if (spfa()) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

POJ 1502 MPI Maelstrom

题意: 实验室有很多台计算机,由于每个人计算机的性能不同,导致计算机之间发送信息的速度不同,所以花费时间不同。
消息从第一台电脑发送到第二台电脑后,这两台电脑能再向其他电脑发送消息,就是一种类似二叉树的结构。
当然并不是真正的二叉树——我们的计算机有一些特殊的特性,我们应该加以利用。
我们的计算机允许同时向连接到它的任意数量的其他计算机发送消息。
然而,消息不一定同时到达目的地——这涉及到计算机配置。
一般来说,我们需要考虑到拓扑结构中每个计算机的时间成本,并相应地计划将消息传播所需的总时间降到最低。
涂爷需要经常给其他人发消息,她想要知道如果她发出一个消息,至少要等多久全部的人才可以都收到通知。

题解: 以1为源点,求出最短路,然后找到最大的那个即可。

代码:

POJ 3660 Cow Contest

题意: n个牛打架 初始已知m个打架结果 求最后能确定具体名次的牛 有几个

题解: 用Floyd确定最后的关系后如果 一个牛打败了x个 被y个打败 且x+y == n-1 则 这个牛的名次则可以确定

代码:

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

using namespace std;

int const MAXN = 110;
int n, m, edge[MAXN][MAXN];

int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int a, b;
        cin >> a >> b;
        edge[a][b] = 1;
    }

    for (int k = 1; k <= n; ++k)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j) edge[i][j] |= edge[i][k] && edge[k][j];

    int res = 0;
    for (int i = 1; i <= n; ++i) {
        int ans = 0;
        for (int j = 1; j <= n; ++j) {
            if (edge[i][j] || edge[j][i]) ans++;
        }
        if (ans == n - 1) res++;
    }
    cout << res << endl;
    return 0;
}

POJ 2240 Arbitrage

题意: 每两种货币之间有一种汇率 给定货币种类与汇率 问一种货币能否经过兑换后价值增大

题解: floyd很版。直接看别人代码吧:https://www.cnblogs.com/zhangdashuai/p/3772892.html

代码:

#include<iostream>
#include<cstring>
using namespace std;
double map[111][111];
char s[33][111];
int n;
int get(char s1[])
{
    for(int i=1;i<=n;i++)
        if(strcmp(s[i],s1)==0) return i;
}
bool 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][k]*map[k][j]>map[i][j])
                    map[i][j]=map[i][k]*map[k][j];
    }
    for(i=1;i<=n;i++)
        if(map[i][i]>1) return true;
    return false;
}
int main()
{
    int i,m;
    double rate;
    char s1[111],s2[111];
    int cas=0;
    while(scanf("%d",&n)!=EOF)
    {
        cas++;
        if(n==0) break;
        memset(map,0,sizeof(map));
        for(i=1;i<=n;i++)
            cin>>s[i];
        cin>>m;
        while(m--)
        {
            cin>>s1>>rate>>s2;
            int u=get(s1);
            int v=get(s2);
            map[u][v]=rate;
        }
        if(floyd())
            printf("Case %d: Yes\n",cas);
            
        else printf("Case %d: No\n",cas);
    }
    return 0;
}

POJ 1511 Invitation Cards

题意: n-1个人从1号点出发,到剩余n-1个宣传点,然后再回到1号点汇报结果,求所有人往返路径和的最小值

题解: dijkstrta+反图。正着做一次,反着做一次。然后每次都求累加和。

代码: 水题,不写了

POJ 3159 Candies

题意: 给出n个点,m个关系,然后每个关系输入a,b,c,表示a+c>=b,a、b表示两个点,最后求出1和n最大差值为多少。

题解: 明确的给出了 a b w a比b最多多w,所以 b - a <= w 。 那么由b到a建立有向边,权值为w,跑一次由1到n最短路,得出最多的差值

代码: 附上大佬代码:https://blog.csdn.net/winddreams/article/details/38762299,代码不想写了,挺板子的

POJ 2502 Subway

题意: 一个人从家要到学校去,途中有许多车站,所以有步行和做地铁两种方式,其速度分别是10km/h 和40km/h。输入的规则是第一行输入的是x1,y1,x2,y2,分别代表家的坐标和学校的坐标。以后输入的是车站的坐标,数目不超过200,相邻的两个站点可以坐地铁,其他的需要步行。问到达学校的最短时间是多少?(因为不知道输入的数据有多少,所以用while(scanf()!=EOF)。其他的就没有什么要注意的了,建图很重要。)

题解: 建图,然后简单的最短路

代码: 代码不写了

POJ 1062 昂贵的聘礼

题意: 年轻的探险家来到了一个印第安部落里。在那里他和酋长的女儿相爱了,于是便向酋长去求亲。酋长要他用10000个金币作为聘礼才答应把女儿嫁给他。探险家拿不出这么多金币,便请求酋长降低要求。酋长说:”嗯,如果你能够替我弄到大祭司的皮袄,我可以只要8000金币。如果你能够弄来他的水晶球,那么只要5000金币就行了。”探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。另外他要告诉你的是,在这个部落里,等级观念十分森严。地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。他是一个外来人,所以可以不受这些限制。
但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。因此你需要在考虑所有的情况以后给他提供一个最好的方案。为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品Ti和该替代品所对应的”优惠”Vi。如果两人地位等级差距超过了M,就不能”间接交易”。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。

1≤N≤100,1≤P≤10000,1≤L,M≤N,0≤X<N

题解: 本题要求1~n的路径上有等级差值的限制的最短路。 建立虚拟源点,然后做dijkstra。为了解决等级限制,就暴力去枚举能够使用的等级区间,dijkstra要满足等级区间限制才能进行转移。

代码:

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

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int w[N][N], level[N];
int dist[N];
bool st[N];

// dijkstra时要有等级的限制
int dijkstra(int down, int up) {
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);

    dist[0] = 0;
    for (int i = 1; i <= n + 1; i++) {
        int t = -1;
        for (int j = 0; j <= n; j++)
            if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j;

        st[t] = true;
        for (int j = 1; j <= n; j++)
            if (level[j] >= down && level[j] <= up)  // 满足上下界才能转移
                dist[j] = min(dist[j], dist[t] + w[t][j]);
    }

    return dist[1];
}

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

    memset(w, 0x3f, sizeof w);
    for (int i = 1; i <= n; i++) w[i][i] = 0;

    for (int i = 1; i <= n; i++) {
        int price, cnt;
        cin >> price >> level[i] >> cnt;
        w[0][i] = min(price, w[0][i]);
        while (cnt--) {
            int id, cost;
            cin >> id >> cost;
            w[id][i] = min(w[id][i], cost);
        }
    }

    int res = INF;
    // 枚举和1号点相关的等级区间
    for (int i = level[1] - m; i <= level[1]; i++)
        res = min(res, dijkstra(i, i + m));

    cout << res << endl;

    return 0;
}

POJ 1847 Tram

题意: 有n个点,然后这些点和其他点有些路径,每个点是一个开关,开关只能有一个方向走一条路,而第一个数就是默认的开关指向,不用旋转。就是默认的指向实际上只需要旋转0次,而其他路径只需要旋转1次,无论是哪条,只需1次。求a到b最小的旋转次数

题解: 建完图后。简单的最短路就完事了。

代码: 不想写了

LightOJ 1074 Extended Traffic

题意: 这一晚,TT 做了个美梦!

在梦中,TT 的愿望成真了,他成为了喵星的统领!喵星上有 N 个商业城市,编号 1 ~ N,其中 1 号城市是 TT 所在的城市,即首都。

喵星上共有 M 条有向道路供商业城市相互往来。但是随着喵星商业的日渐繁荣,有些道路变得非常拥挤。正在 TT 为之苦恼之时,他的魔法小猫咪提出了一个解决方案!TT 欣然接受并针对该方案颁布了一项新的政策。

具体政策如下:对每一个商业城市标记一个正整数,表示其繁荣程度,当每一只喵沿道路从一个商业城市走到另一个商业城市时,TT 都会收取它们(目的地繁荣程度 - 出发地繁荣程度)^ 3 的税。

TT 打算测试一下这项政策是否合理,因此他想知道从首都出发,走到其他城市至少要交多少的税,如果总金额小于 3 或者无法到达请悄咪咪地打出 ‘?’。

题解: 建边跑最短路。

代码: 不写了

HDU 4725 The Shortest Path in Nya Graph

题意: 求1-n最短路,每个点有一个层数,相邻层之间花费k可以到达

题解: 建图。把每一层看作一个点,然后跑最短路

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int INF = 0xfffffff;
struct Edge {
    int s, t, v, nxt;
} e[1000005];
int n, m, cnt, head[1000005], dis[1000005], vis[1000005];
void add(int s, int t, int v) {
    e[cnt].s = s;
    e[cnt].t = t;
    e[cnt].v = v;
    e[cnt].nxt = head[s];
    head[s] = cnt++;
}
struct Heap {
    int s, d;
    friend bool operator<(Heap a, Heap b) { return a.d > b.d; }
};
int Dijkstra(int s, int t, int N) {
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= N; i++) dis[i] = INF;
    dis[s] = 0;
    priority_queue<Heap> q;
    Heap st;
    st.s = s;
    st.d = dis[s];
    q.push(st);
    while (!q.empty()) {
        Heap u = q.top();
        q.pop();
        if (u.s == t) return u.d;
        if (vis[u.s]) continue;
        vis[u.s] = 1;
        for (int i = head[u.s]; i != -1; i = e[i].nxt) {
            int tt = e[i].t;
            if (!vis[tt] && dis[tt] > e[i].v + dis[u.s]) {
                dis[tt] = e[i].v + dis[u.s];
                Heap ttt;
                ttt.s = tt;
                ttt.d = dis[tt];
                q.push(ttt);
            }
        }
    }
    return -1;
}
int flag[1000005], L[1000005];
int main() {
    int t;
    scanf("%d", &t);
    for (int cas = 1; cas <= t; cas++) {
        cnt = 0;
        memset(head, -1, sizeof(head));
        int c;
        scanf("%d%d%d", &n, &m, &c);
        memset(flag, 0, sizeof(flag));
        for (int i = 1; i <= n; i++) {
            scanf("%d", &L[i]);
            flag[L[i] + n] = 1;
            add(L[i] + n, i, 0);
        }
        for (int i = 1; i <= n; i++) {
            if (L[i] > 1 && flag[L[i] + n - 1]) add(i, L[i] + n - 1, c);
            if (L[i] < n && flag[L[i] + n + 1]) add(i, L[i] + n + 1, c);
        }
        for (int i = n + 1; i < 2 * n; i++) {
            if (flag[i] && flag[i + 1]) {
                add(i, i + 1, c);
                add(i + 1, i, c);
            }
        }
        while (m--) {
            int s, t, v;
            scanf("%d%d%d", &s, &t, &v);
            add(s, t, v);
            add(t, s, v);
        }
        printf("Case #%d: %d\n", cas, Dijkstra(1, n, 2 * n));
    }
    return 0;
}

HDU 3416 Marriage Match IV

题意: 有 n 个城市,知道了起点和终点,有 m 条有向边,问从起点到终点的最短路一共有多少条。(一条边只能走一次)

题解: 最短路+网络流。先通过最短路,然后将不在最短路上边删掉,判断一条边是在最短路上:dis[a] + c == dis[b]。然后重新建图,每条在最短路上的边权值改为1,然后跑最大流即可。

代码:

#include <bits/stdc++.h>

using namespace std;

#define int long long
typedef pair<int, int> PII;
int const N = 1050, M = 200500, INF = 1e18 + 10;
int e[M], ne[M], w[M], h[N], idx, n, m, dis[N], st[N], TT, T, S, f[M];
int d[N], cur[N];
struct Edge {
    int u, v, c;
}edge[M];

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

void add2(int a, int b, int c) {
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}

// bfs找是否存在增广路
int bfs() {
    queue<int> q;
    memset(d, -1, sizeof d);
    q.push(S);
    d[S] = 0;
    cur[S] = h[S];
    while(q.size()) {
        auto t = q.front();
        q.pop();
        for (int i = cur[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] != -1 || !f[i]) continue;  
            d[j] = d[t] + 1;  // 更新分层图
            cur[j] = h[j];  // 当前弧优化,cur[j]记录j点第一条可以访问的边
            if (j == T) return 1;
            q.push(j);
        }
    }
    return 0;
}

// dfs把增广路更新
int find(int u, int limit) {
    if (u == T) return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {  // 当前弧优化+流量限制
        cur[u] = i;
        int j = e[i];
        if (d[j] != d[u] + 1 || !f[i]) continue;  // 必须在分层图上,防止出现环;必须有流量
        int t = find(j, min(f[i], limit - flow));  // 找到从j出去的流量
        if (!t) d[j] = -1;  // 流量为0,说明这条路不行
        f[i] -= t, f[i ^ 1] += t, flow += t;  // 更新流量
    }
    return flow;
}

int dinic() {  
    int res = 0, flow = 0;
    // 每次判断是否存在增广路(bfs),如果存在,那么把所有的增广路更新(find)
    while (bfs()) while(flow = find(S, INF)) res += flow; 
    return res;
}

// 堆优化版dijksyta
void dijkstra() {
    for (int i = 1; i <= n; ++i) dis[i] = 0x3f3f3f3f3f3f3f3f, st[i] = 0;
    priority_queue<PII, vector<PII>, greater<PII> >
        q;  // 定义一个按照距离从小到大排序的优先队列,第一维:距离,第二维:点
    dis[S] = 0;         // 一开始源点距离为0
    q.push({0, S});     // 把源点信息放入队列
    while (q.size()) {  // 每个点只出入队列一次
        auto t = q.top();
        q.pop();

        int distance = t.first, ver = t.second;  // 最小距离和相对应的点
        if (st[ver])
            continue;  // 这个操作保证每个点只出入队一次,因为队列里面可能会出现{dis1[3],
                       // 3}, {dis2[3],
                       // 3}的情况,这样保证dis1[3]<dis2[3]时,3号点只进出入队一次
        st[ver] = 1;  // 标记,因为dijkstra的贪心策略保证每个点只需要进出队一次

        for (int i = h[ver]; ~i; i = ne[i]) {  // 遍历ver的邻接点
            int j = e[i];
            if (dis[j] > distance + w[i]) {
                dis[j] = distance + w[i];
                q.push(
                    {dis[j],
                     j});  // 这里不需要判断st,因为一旦更新发现更小必须放入队列
            }
        }
    }
}

signed main() {
    cin >> TT;
    while(TT--) {
        cin >> n >> m;
        idx = 0;
        for (int i = 1; i <= n; ++i) h[i] = -1;
        for (int i = 1, a, b, c; i <= m; ++i) {  // 读入m条边
            scanf("%lld%lld%lld", &a, &b, &c);
            edge[i] = {b, a, c};
            add(a, b, c);
        }
        cin >> S >> T;
        dijkstra();

        idx = 0;
        for (int i = 1; i <= n; ++i) h[i] = -1;
        for (int i = 1; i <= m; ++i) {
            int a = edge[i].v, b = edge[i].u, c = edge[i].c;
            if (dis[a] + c == dis[b]) add2(a, b, 1);
        }

        printf("%lld\n", dinic());
    }
    return 0;
}

HDU 4370 0 or 1

题意: 给定n * n矩阵C ij(1 <= i,j <= n),我们要找到0或1的n * n矩阵X ij(1 <= i,j <= n)。
此外,X ij满足以下条件:
1.X 12 + X 13 + … X 1n = 1
2.X 1n + X 2n + … X n-1n = 1
3.对于每个i(1 <i <n),满足ΣXki(1 <= k <= n)=ΣXij(1 <= j <= n)。
例如,如果n = 4,我们可以得到以下等式:
X 12 + X 13 + X 14 = 1
X 14 + X 24 + X 34 = 1
X 12 + X 22 + X 32 + X 42 = X 21 + X 22 + X 23 + X 24
X 13 + X 23 + X 33 + X 43 = X 31 + X 32 + X 33 + X 34
现在,我们想知道你可以得到的最小ΣCij * X ij(1 <= i,j <= n)。1<n<=300

题解: 3个条件明显在刻画未知数之间的关系,从图论的角度思考问题,容易得到下面3个结论:

1.X12+X13+…X1n=1 于是1号节点的出度为1

2…X1n+X2n+…Xn-1n=1 于是n号节点的入度为1

3.∑Xki =∑Xij 于是2~n-1号节点的入度必须等于出度

将矩阵理解为边上的权值,那么可以理解为:

一:1号点到n号点的花费

二:1号点经过其它点成环,n号点经过其它点成环,这两个环的花费之和

因此我们就需要找到经过1点的和n点的最小环,这个使用spfa算法可以魔改得到。其他的就是找1 ~ n的最短路。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 310, INF = 0x3f3f3f3f;
int st[N], mp[N][N], dis[N], n;

// spfa求最长路
void spfa(int s) {
    queue<int> q;  // 建立新队列
    for (int i = 1; i <= n; ++i) {
        st[i] = 0;
        if (i == s) dis[i] = INF;  // 初始化要求最小环的点
        else {
            dis[i] = mp[s][i];
            st[i] = 1;
            q.push(i);
        }
    }
    
    while (q.size()) {
        auto t = q.front();  
        q.pop();  // 队首元素出队
        st[t] = 0;  // 记录出队

        for (int i = 1; i <= n; ++i) {
            if (dis[i] > dis[t] + mp[t][i]) {
                dis[i] = dis[t] + mp[t][i];
                if (!st[i]) {  // 如果j点不在队列里
                    q.push(i);  // 入队
                    st[i] = 1;  // 记录
                }
            }
        }
    }
}

int main() {
    while (scanf("%d", &n) != EOF) {
        for (int i = 1; i <= n; ++i)   
            for (int j = 1; j <= n; ++j) scanf("%d", &mp[i][j]);

        spfa(1);
        int ans1 = dis[1];
        int ans = dis[n];

        spfa(n);
        int ans2 = dis[n];
        printf("%d\n", min(ans, ans1 + ans2));
    }
    return 0;
}

POJ 3169 Layout

题意: N头奶牛站成一排,有M1对关系和M2对关系。M1对关系希望A和B至多相隔L,M2对关系希望A和B至少相隔D。输出一个整数,如果不存在满足要求的方案,输出-1;如果 1 号奶牛和 N 号奶牛间的距离可以任意大,输出-2;否则,输出在满足所有要求的情况下,1 号奶牛和 N 号奶牛间可能的最大距离。
题解: 本题求解最大值,就是跑最短路,得到i<=j+c的关系建图,然后spfa跑最短路,由于要求1号点到n号点的最短距离,所以直接把1号点作为源点
如果存在负环,那么输出-1; 如果不存在负环,但dis[n]=0x3f3f3f3f,那么-2;否则,输出dis[n]。本题需要注意的是,由于1号点可能为孤立点,因此如果直接把1号点放入队列,其他不放入队列,那么可能判不出负环,因此需要把所有的点都放入队列。
代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 1010, M = 10000 + 10000 + 1000 + 10, INF = 0x3f3f3f3f;

int n, m1, m2;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int q[N], cnt[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 ++ ;
}

// spfa求负环(正环)
bool spfa() {
    queue<int> q;
    
    memset(dist, 0x3f, sizeof dist);
    memset(cnt, 0, sizeof cnt);
    memset(st, 0, sizeof st);
    for (int i = 1; i <= n; i ++ ) {
        st[i] = true;
        q.push(i);
    }
    dist[1] = 0;  
    while (q.size())  {
        int t = q.front();  // 取队首
        q.pop();  // 出队首

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[t] + w[i]) { // 这里是判断负环,如果是判正环:1.初始化写成memset(dis, 0xc0, sizeof dis), 2.更新条件写成dist[j] < dist[t] + w[i]
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;  // 更新边数
                if (cnt[j] >= N) return true;  // 如果j点到源点的边数大于等于n
                if (!st[j]) {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

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

    for (int i = 1; i < n; i ++ ) add(i + 1, i, 0);
    while (m1 -- ) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        if (a > b) swap(a, b);
        add(a, b, c);
    }
    while (m2 -- ) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        if (a > b) swap(a, b);
        add(b, a, -c);
    }
    
    if (spfa()) printf("-1");
    else {
        if (dist[n] == 0x3f3f3f3f) printf("-2\n");
        else printf("%d", dist[n]);
    }

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值