最短路

最短路

单源最短路径

Dijkstra算法O(mlogn)

每次找到离出发点距离最近的且未被使用过的点,用此点去更新其他点。

1.初始化\(dis[1]=0\),其余节点dis值为正无穷。

2.找出一个未被标记的,dis[u]最小的节点x,然后标记节点u。

3.扫描节点u的所有出边(u,v,w),若\(dis[v]>dis[u]+w\),更新节点,将节点加入二叉堆

4.重复步骤2~3,直到所有节点都被标记。
Dijkstra算法基于贪心思想,只适用于所有边都是非负数的图。Dijkstra使用二叉堆储存节点,由于每次取出的必定是队列中的距离最小的节点,所以每个节点最多进队一次,二叉堆加入和删除复杂度为\(O(logn)\),一共最多进行m次,则时间复杂度为\(O(mlogn)\)

可以处理到想要的点就结束程序。

不能跑负权的原因:dij跑到终点就结束了,但有可能q堆里目前比终点距离远的点,即还未出队的点,会跑到一个负边,加上负边权之后到终点的距离反而会更小。

void Dijkstra()
{
	memset(d,0x3f,sizeof(d));
	memset(v,0,sizeof(v));
	d[1]=0;
	q.push(make_pair(0,1));
	
	while(q.size())
	{
		int u=q.top().second;
		q.pop();
        if(u==n) break;
		if(v[u]) continue;
		v[u]=1;
		for(int i=tail[u];i;i=e[i].pre)
		{
			int v=e[i].v,w=e[i].w;
			if(d[v]>d[u]+w)
			{
				d[v]=d[u]+w;
				q.push(make_pair(-d[v],v));
			}
		}
	}
}

SPFA算法O(km)~O(mn)

即Bellman-Ford算法的队列优化。

Bellman-Ford算法:扫描所有的边以更新值。

SPFA算法:将已经更新了的点放入一个集合,每次从本集合中取点更新其他点,如果被更新的点不在集合内,则将被更新的点加入集合。(如果一个点未被更新,则其无法更新其他点的值,故直接跳过进行扫描该点的边这一操作,只执行可更新别的值的点)

一般而言,SPFA算法的时间复杂度为\(O(km)\),其中k是一个较小的常数。但由于SPFA的队列中的点顺序是随机的,每个点的大小顺序没有规律,故可能出现一个点被多次更新,多次入队。如图:

image-20200507204214050

故在特殊构造的图上,时间复杂度可能退化为\(O(mn)\)

SPFA用双端队列储存节点。

必须处理完所有节点。

#include <bits/stdc++.h>

using namespace std;

struct Edge {
	int u, v, t;
};

vector<Edge> Node[1005];

void addedge(int u, int v, int t) {
	Node[u].push_back ((Edge){u, v, t});
	return;
}

int N, P, K;

int dis[1005],inq[1005];

int spfa(int V) {
	for (int i = 1; i <= N; i += 1) {
		dis[i] = 10005;
	}
	memset (inq, 0, sizeof inq);
	queue<int> q;
	q.push (1);
	inq[1] = 1;
	dis[1] = 0;
	while (!q.empty ()) {
		int now = q.front ();
		q.pop ();
		inq[now] = 0;
		for (int i = 0; i < (int) Node[now].size (); i += 1) {
			if ((Node[now][i].t > V) + dis[Node[now][i].u] < dis[Node[now][i].v]) {
				dis[Node[now][i].v] = (Node[now][i].t > V) + dis[Node[now][i].u];
				if (!inq[Node[now][i].v]) {
					q.push (Node[now][i].v);
				}
			}
		}
	}
	return dis[N];
}

int main() {
	cin >> N >> P >> K;
	for (int i = 0; i < P; i += 1) {
		int A, B, L;
		cin >> A >> B >> L;
		addedge(A, B, L);
		addedge(B, A, L);
	}
	int l = 0, r = 1000001;
	while (l < r) {
		int mid = (l + r) / 2;
		int y = spfa (mid);
		if (y > K) {
			l = mid + 1;
		} else {
			r = mid;
		}
	}
	if (l != 1000001) {
		cout << l << endl;
	} else {
		cout << -1 << endl;
	}
	return 0;
}
通信线路

在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站AiAi和BiBi。

特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。

现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费LiLi。

电话公司正在举行优惠活动。

农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。

农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。

求至少用多少钱可以完成升级。

输入格式

第1行:三个整数N,P,K。

第2..P+1行:第 i+1 行包含三个整数Ai,Bi,LiAi,Bi,Li。

输出格式

包含一个整数表示最少花费。

若1号基站与N号基站之间不存在路径,则输出”-1”。

数据范围

0≤K<N≤10000≤K<N≤1000,
1≤P≤100001≤P≤10000,
1≤Li≤10000001≤Li≤1000000

输入样例:

5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6

输出样例:

4

二分ans,查找如果大于ans的就不付钱需要多少条线路,如果线路数比k大则更新l,如果小于等于k则更新r(找到符合条件的最小值)。

#include <bits/stdc++.h>

using namespace std;

struct Edge {
	int u, v, t;
};

vector<Edge> Node[1005];

void addedge(int u, int v, int t) {
	Node[u].push_back ((Edge){u, v, t});
	return;
}

int N, P, K;

int dis[1005],inq[1005];

int spfa(int V) {
	for (int i = 1; i <= N; i += 1) {
		dis[i] = 10005;
	}
	memset (inq, 0, sizeof inq);
	queue<int> q;
	q.push (1);
	inq[1] = 1;
	dis[1] = 0;
	while (!q.empty ()) {
		int now = q.front ();
		q.pop ();
		inq[now] = 0;
		for (int i = 0; i < (int) Node[now].size (); i += 1) {
			if ((Node[now][i].t > V) + dis[Node[now][i].u] < dis[Node[now][i].v]) {
				dis[Node[now][i].v] = (Node[now][i].t > V) + dis[Node[now][i].u];
				if (!inq[Node[now][i].v]) {
					q.push (Node[now][i].v);
				}
			}
		}
	}
	return dis[N];
}

int main() {
	cin >> N >> P >> K;
	for (int i = 0; i < P; i += 1) {
		int A, B, L;
		cin >> A >> B >> L;
		addedge(A, B, L);
		addedge(B, A, L);
	}
	int l = 0, r = 1000001;
	while (l < r) {
		int mid = (l + r) / 2;
		int y = spfa (mid);
		if (y > K) {
			l = mid + 1;
		} else {
			r = mid;
		}
	}
	if (l != 1000001) {
		cout << l << endl;
	} else {
		cout << -1 << endl;
	}
	return 0;
}
最优贸易

C国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。

任意两个城市之间最多只有一条道路直接相连。

这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。

C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。

但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到C国旅游。

当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。

设C国 n 个城市的标号从 1~n,阿龙决定从1号城市出发,并最终在 n 号城市结束自己的旅行。

在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。

阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。

因为阿龙主要是来C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。

请你告诉阿龙,他最多能赚取多少旅费。

注意:本题数据有加强。

输入格式

第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。

第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。

接下来 m 行,每行有 3 个正整数,x,y,z,每两个整数之间用一个空格隔开。

如果z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果z=2,表示这条道路为城市 x 和城市 y 之间的双向道路。

输出格式

一个整数,表示答案。

数据范围

1≤n≤1000001≤n≤100000,
1≤m≤5000001≤m≤500000,
1≤各城市水晶球价格≤1001≤各城市水晶球价格≤100

输入样例:

5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2

输出样例:

5

正向从1~n找到每个从城市1到城市i的最低买价。

反向从n~1找到每个从城市i到城市n的最高卖家。

由于要构建反向图,所以要另开一个数组反向储存所有边。

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

const int N=1e5+5,M=5e5+5;

int n,m,te,ans,a[N],tai[N],tail[N],buy[N],sell[N];
bool vis[N];
struct e_
{
	int v,pre;
}e[M*2],ee[M*2];

inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};
	tail[u]=te;
	ee[te]=(e_){u,tai[v]};
	tai[v]=te;
}

inline int read()
{
	int res=0;
	char dd=getchar();
	while(dd<'0'||dd>'9') dd=getchar();
	while(dd<='9'&&dd>='0') res=(res<<1)+(res<<3)+dd-'0',dd=getchar();
	return res;
}

void SPFA_BUY()
{
	memset(buy,0x3f,sizeof(buy));
	
	deque<int>q;
	buy[1]=a[1];
	q.push_back(1);
	
	while(q.size())
	{
		int u=q.front();
		q.pop_front();
		
		vis[u]=0;
		
		for(int i=tail[u];i;i=e[i].pre)
		{
			int v=e[i].v,val=min(buy[u],a[v]);
			if(buy[v]>val)
			{
				buy[v]=val;
				if(!vis[v])
				{
					vis[v]=1;
					
					if(q.empty()||buy[q.front()]<buy[v]) q.push_back(v);
					else q.push_front(v);
				}
			}
		}
	}
}
void SPFA_SELL()
{
	memset(vis,0,sizeof(vis));
	
	deque<int>q;
	sell[n]=a[n];
	q.push_back(n);
	
	while(q.size())
	{
		int u=q.front();
		q.pop_front();
		
		vis[u]=0;
		
		for(int i=tai[u];i;i=ee[i].pre)
		{
			int v=ee[i].v,val=max(sell[u],a[v]);
			if(sell[v]<val)
			{
				sell[v]=val;
				if(!vis[v])
				{
					vis[v]=1;
					
					if(q.empty()||sell[q.front()]>sell[v]) q.push_back(v);
					else q.push_front(v);
				}
			}
		}
	}
}
int main()
{
	n=read();
	m=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1,u,v,z;i<=m;++i)
	{
		u=read();v=read();z=read();
		add(u,v);
		if(z==2) add(v,u);
	}
	SPFA_BUY();
	SPFA_SELL();
	
	for(int i=1;i<=n;++i) ans=max(ans,sell[i]-buy[i]);
	printf("%d\n",ans);
}
道路与航线

农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。

他想把牛奶送到T个城镇,编号为1~T。

这些城镇之间通过R条道路 (编号为1到R) 和P条航线 (编号为1到P) 连接。

每条道路i或者航线i连接城镇Ai到Bi,花费为Ci。

对于道路,0≤Ci≤10,000然而航线的花费很神奇,花费Ci可能是负数(−10,000≤Ci≤10,000)。

道路是双向的,可以从Ai到Bi,也可以从Bi到Ai,花费都是Ci。

然而航线与之不同,只可以从Ai到Bi。

事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从Ai到Bi,那么保证不可能通过一些道路和航线从Bi回到Ai。

由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。

他想找到从发送中心城镇S把奶牛送到每个城镇的最便宜的方案。

输入格式

第一行包含四个整数T,R,P,S。

接下来R行,每行包含三个整数(表示一个道路)Ai,Bi,CiAi,Bi,Ci。

接下来P行,每行包含三个整数(表示一条航线)Ai,Bi,Ci

输出格式

第1..T行:第i行输出从S到达城镇i的最小花费,如果不存在,则输出“NO PATH”。

数据范围

1≤T≤25000
1≤R,P≤50000
1≤Ai,Bi,S≤T

输入样例:

6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10

输出样例:

NO PATH
NO PATH
5
0
-95
-100

分析题意已知,可通过道路形成多个不同的联通块,可通过航线将不同的联通块连接。

1.预处理出每个节点属于哪一个联通块,有多少航线可以到达此联通块。

2.使S所在的联通块入度为0,将所在的联通块加入ready。

3.每次取出堆顶更新v,然后再进行判断:

如果u,v在同一联通块,即可以直接到达,则跳过。

如果u,v不在同一联通块,即需要经过航线,使v所在的联通块入度减一。

如果v所在的联通块入度不为1,即还有别的航线可以到达该联通块,该联通块可以由别的联通块更新的节点还未更新完,则跳过。

如果v所在的联通块入度为0,即对v所在联通块可以由别的联通块更新的节点已经更新完,将v所在的联通块加入额外的数组ready。

此处为了操作方便,我们将入度减到0时的v代替联通块编号直接加入ready。然后再进行\(dijkstra\)操作。

此处为了更方便,我们直接用SPFA(想不到吧!)

#include<bits/stdc++.h>
using namespace std;
const int N=25e3+5,M=5e4+5;
int n,r,p,s,te,cnt[N],tail[N],dis[N];
bool vis[N];
struct e_
{
	int v,w,pre;
}e[M*4];
inline void add(int u,int v,int w)
{
	e[++te]=(e_){v,w,tail[u]};
	tail[u]=te;
}

void SPFA()
{
	memset(dis,0x3f,sizeof(dis));

	dis[s]=0;
	deque<int>q;

	q.push_back(s);

	while(q.size())
	{
		int u=q.front();
		q.pop_front();

		vis[u]=0;

		for(int i=tail[u];i;i=e[i].pre)
		{
			int v=e[i].v,w=e[i].w;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				if(!vis[v])
				{
					vis[v]=1;
					if(q.size()&&dis[q.front()]>dis[v]) q.push_front(v);
					else q.push_back(v);
				}
			}
		}
	}
}
int main()
{
	scanf("%d %d %d %d",&n,&r,&p,&s);

	for(int i=1,u,v,w;i<=r;++i)
	{
		scanf("%d %d %d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	for(int i=1,u,v,w;i<=p;++i)
	{
		scanf("%d %d %d",&u,&v,&w);
		add(u,v,w);
	}

	SPFA();

	for(int i=1;i<=n;++i)
	if(dis[i]==dis[0]) printf("NO PATH\n");
	else printf("%d\n",dis[i]);
}

DFS判断负环

利用dfs,将d数组全部赋值为0,相当于有且只有d[u]+w<0时可移动,如果移动到一个已经扫描过的点,这存在负环。

Easy SSSP

描述
输入数据给出一个有N(2≤N≤10002≤N≤1000)个节点,M(M≤106M≤106)条边的带权有向图.
要求你写一个程序, 判断这个有向图中是否存在负权回路. 如果从一个点沿着某条路径出发, 又回到了自己, 而且所经过的边上的权和小于0, 就说这条路是一个负权回路.
如果存在负权回路, 只输出一行-1;
如果不存在负权回路, 再求出一个点S(1≤S≤N1≤S≤N)到每个点的最短路的长度. 约定: S到S的距离为0, 如果S与这个点不连通, 则输出NoPath.

格式
输入格式
第一行: 点数N(2≤N≤10002≤N≤1000), 边数M(M≤106M≤106), 源点S(1≤S≤N1≤S≤N);
以下M行, 每行三个整数a, b, c表示点a, b(1≤a,b≤N1≤a,b≤N)之间连有一条边, 权值为c(−1,000,000≤c≤1,000,000−1,000,000≤c≤1,000,000)
输出格式
如果存在负权环, 只输出一行-1, 否则按以下格式输出
共N行, 第i行描述S点到点i的最短路:
如果S与i不连通, 输出NoPath;
如果i = S, 输出0;
其他情况输出S到i的最短路的长度.

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e3 + 5, M = 1e5 + 5;
int n, m, st, te, tail[N];
ll d[N];
bool vis[N], flag;
struct e_ {
    int v, pre;
    ll w;
} e[M * 2];

inline int read() 
{
    char dd = getchar();
    int res = 0, w = 1;
    while (dd < '0' || dd > '9') {
        if (dd == '-')
            w = -1;
        dd = getchar();
    }
    while (dd >= '0' && dd <= '9') res = (res << 1) + (res << 3) + dd - '0', dd = getchar();
    return res * w;
}

inline void add(int u, int v, ll w) 
{
    e[++te] = (e_){ v, tail[u], w };
    tail[u] = te;
}

void dfs(int u) {
    vis[u] = 1;
    for (int i = tail[u]; i && flag; i = e[i].pre) 
    {
        int v = e[i].v, w = e[i].w;

        if (d[v] > d[u] + w) 
        {
            d[v] = d[u] + w;
            if (vis[v])
            {
                flag = 0;
                return;
            }

            dfs(v);
        }
    }

    vis[u] = 0;
}

void spfa() 
{
    memset(d, 0x3f, sizeof(d));
    memset(vis, 0, sizeof(vis));

    d[st] = 0;

    deque<int> q;
    q.push_back(st);

    while (q.size()) {
        int u = q.front();

        vis[u] = 0;
        q.pop_front();

        for (int i = tail[u]; i; i = e[i].pre) {
            int v = e[i].v, w = e[i].w;
            if (d[v] > d[u] + w) {
                d[v] = d[u] + w;

                if (!vis[v]) {
                    vis[v] = 1;

                    if (q.empty() || d[q.front()] < d[v])
                        q.push_back(v);
                    else
                        q.push_front(v);
                }
            }
        }
    }
}

int main() {
    n = read();
    m = read();
    st = read();

    for (int i = 1, u, v, w; i <= m; ++i) {
        u = read();
        v = read();
        w = (ll)read();
        add(u, v, w);
    }

    flag = 1;
    for (int i = 1; i <= n && flag; ++i) dfs(i);

    if (!flag) {
        printf("-1\n");
        return 0;
    }

    spfa();

    for (int i = 1; i <= n; ++i) d[i] != d[0] ? printf("%d\n", d[i]) : printf("NoPath\n");
}
虫洞(wormhole)

【题目描述】

John在他的农场中闲逛时发现了许多虫洞。虫洞可以看作一条十分奇特的有向边,并可以使你返回到过去的一个时刻(相对你进入虫洞之前)。John的每个农场有M条小路(无向边)连接着N (从1..N标号)块地,并有W个虫洞(有向边)。其中1<=N<=500,1<=M<=2500,1<=W<=200。 现在John想借助这些虫洞来回到过去(出发时刻之前),请你告诉他能办到吗。 John将向你提供F(1<=F<=5)个农场的地图。没有小路会耗费你超过10000秒的时间,当然也没有虫洞回帮你回到超过10000秒以前。

【输入格式】

  • Line 1: 一个整数 F, 表示农场个数。

  • Line 1 of each farm: 三个整数 N, M, W。

  • Lines 2..M+1 of each farm: 三个数(S, E, T)。表示在标号为S的地与标号为E的地中间有一条用时T秒的小路。

  • Lines M+2..M+W+1 of each farm: 三个数(S, E, T)。表示在标号为S的地与标号为E的地中间有一条可以使John到达T秒前的虫洞。

【输出格式】

  • Lines 1..F: 如果John能在这个农场实现他的目标,输出"YES",否则输出"NO"。

【样例输入】

2

3 3 1

1 2 2

1 3 4

2 3 1

3 1 3

3 2 1

1 2 3

2 3 4

3 1 8

【样例输出】

NO

YES

【题解】

  判断有无负权回路

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

const int N = 505, M = 2605;
int t, n, m, r, te, tail[N], d[N];
bool vis[N], flag;
struct e_ 
{
    int v, w, pre;
} e[M * 2];

inline int read() 
{
    char dd = getchar();
    int res = 0;
    while (dd < '0' || dd > '9') dd = getchar();
    while (dd >= '0' && dd <= '9') res = (res << 1) + (res << 3) + dd - '0', dd = getchar();
    return res;
}

inline void init() 
{
    te = 0;
    memset(tail, 0, sizeof(tail));
    memset(vis, 0, sizeof(vis));
    memset(d, 0, sizeof(d));
}
inline void add(int u, int v, int w) 
{
    e[++te] = (e_){ v, w, tail[u] };
    tail[u] = te;
}

void dfs(int u) 
{
    vis[u] = 1;
    for (int i = tail[u]; i && flag; i = e[i].pre) 
	{
        int v = e[i].v, w = e[i].w;

        if (d[v] > d[u] + w)
		{
            d[v] = d[u] + w;
            if (vis[v]) 
			{
                flag = 0;
                return;
            }

            dfs(v);
        }
    }

    vis[u] = 0;
}

int main() 
{
    t = read();
    while (t--) 
	{
        init();

        n = read();
        m = read();
        r = read();

        for (int i = 1, u, v, w; i <= m; ++i) 
		{
            u = read();
            v = read();
            w = read();
            add(u, v, w);
            add(v, u, w);
        }
        for (int i = 1, u, v, w; i <= r; ++i) 
		{
            u = read();
            v = read();
            w = read();
            add(u, v, -w);
        }
        flag = 1;
        for (int i = 1; i <= n && flag; ++i) dfs(i);

        if (!flag)
            printf("YES\n");
        else
            printf("NO\n");
    }
}

任意两点间最短路径

Floyed算法O(N3

枚举中转节点,更新i到j的值。

输入时先使\(f[i][j]\)为正无穷,再输入\(f[u][v]\)

for(int k=1;k<=n;++k)
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

i,j,k顺序不可调换。

可以使用Floyed算法判断条件的成立与否。

输入时先使\(f[i][j]\)为0,再使\(f[u][v]=1\)

排序

给定 n个变量和 m 个不等式。其中 n 小于等于26,变量分别用前 n

的大写英文字母表示。

不等式之间具有传递性,即若 A>B 且 B>C ,则 A>C。

请从前往后遍历每对关系,每次遍历时判断:

  • 如果能够确定全部关系且无矛盾,则结束循环,输出确定的次序;
  • 如果发生矛盾,则结束循环,输出有矛盾;
  • 如果循环结束时没有发生上述两种情况,则输出无定解。

输入格式

输入包含多组测试数据。

每组测试数据,第一行包含两个整数n和m。

接下来m行,每行包含一个不等式,不等式全部为小于关系。

当输入一行0 0时,表示输入终止。

输出格式

每组数据输出一个占一行的结果。

结果可能为下列三种之一:

  1. 如果可以确定两两之间的关系,则输出 "Sorted sequence determined after t relations: yyy...y.",其中't'指迭代次数,'yyy...y'是指升序排列的所有变量。
  2. 如果有矛盾,则输出: "Inconsistency found after t relations.",其中't'指迭代次数。
  3. 如果没有矛盾,且不能确定两两之间的关系,则输出 "Sorted sequence cannot be determined."

数据范围

2≤n≤26

,变量只可能为大写字母A~Z。

输入样例1:

4 6
A<B
A<C
B<C
C<D
B<D
A<B
3 2
A<B
B<A
26 1
A<Z
0 0

输出样例1:

Sorted sequence determined after 4 relations: ABCD.
Inconsistency found after 2 relations.
Sorted sequence cannot be determined.

输入样例2:

6 6
A<F
B<D
C<E
F<D
D<E
E<F
0 0

输出样例2:

Inconsistency found after 6 relations.

输入样例3:

5 5
A<B
B<C
C<D
D<E
E<A
0 0

输出样例3:

Sorted sequence determined after 4 relations: ABCDE.

#include<bits/stdc++.h>
using namespace std;
int n,m;
char a,b;
int rood[30][30];
int MAX;
void flyd()//floyd求最长路 
{
    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                if(rood[i][k]&&rood[k][j])
                {
                    rood[i][j]=max(rood[i][j],rood[i][k]+rood[k][j]);
                    MAX=max(MAX,rood[i][j]);//如果 MAX==n-1 那么排序成功
                }
}
int main()
{
    while(1)
    {
        memset(rood,0,sizeof(rood));
        cin>>n>>m;
        if(!n&&!m)
            break;//嗯,退出 
        int flag=1;//十分普通的标记 
        MAX=0;//见flyd函数注释 
        for(int i=1;i<=m;i++)
        {
            scanf(" %c<%c",&a,&b);//输入 
            int from=b-'A';//强制类型转换 
            int to=a-'A';//同上 
            if(rood[to][from])//判断是否矛盾 
            {
                printf("Inconsistency found after %d relations.\n",i);
                for(int j=i+1;j<=m;j++)//吸收下面的输入 
                    scanf(" %c<%c",&a,&b);
                flag=0;//标记 
                break;//退出 
            }
            rood[from][to]=1;//标记 
            flyd();
            int head=-1;//最大的数 
            for(int i=0;i<n;i++)
            {
                int f=1;
                for(int j=0;j<n&&f;j++)
                    if(rood[j][i])
                        f=0;
                if(f)
                    head=head==-1?i:233;//如果head==-1则赋值为i,如果head!=-1,说明最大值有两个了,赋为特殊值 
            }
            if(head!=-1&&head!=233&&MAX==n-1)//判断是否合法 
            {
                for(int j=i+1;j<=m;j++)//吸收下面的输入 
                    scanf(" %c<%c",&a,&b);
                cout<<"Sorted sequence determined after "<<i<<" relations: ";
                for(int i=n-1;i>0;i--)//输出 
                    for(int j=0;j<n;j++)
                        if(rood[head][j]==i)
                            cout<<char(j+'A');
                cout<<char(head+'A')<<".\n";
                flag=0;
                break;//退出 
            }
        }
        if(flag)//上面两种情况都不满足 
            puts("Sorted sequence cannot be determined.");
    }
    return 0;
}
观光之旅

给定一张无向图,求图中一个至少包含3个点的环,环上的节点不重复,并且环上的边的长度之和最小。

该问题称为无向图的最小环问题。

你需要输出最小环的方案,若最小环不唯一,输出任意一个均可。

输入格式

第一行包含两个整数N和M,表示无向图有N个点,M条边。

接下来M行,每行包含三个整数u,v,l,表示点u和点v之间有一条边,边长为l。

输出格式

输出占一行,包含最小环的所有节点(按顺序输出),如果不存在则输出’No solution.’。

数据范围

1≤N≤100
1≤M≤10000,
1≤l<500

输入样例:

5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20

输出样例:

1 3 5 2

每次用a数组判断能否连接i和j,a数组为最初输入的边,如图

img如果能连接,则:

void get_path(int u,int v)
{
	if(!pos[u][v]) return;
	get_path(u,pos[u][v]);//找在中转点之前的
	path.push_back(pos[u][v]);//加入中转
	get_path(pos[u][v],v);//找在中转点之后的
}

int main()
{
	...
		if(d[i][j]+a[j][k]+a[k][i]<ans)
		{
			ans=d[i][j]+a[j][k]+a[k][i];
			path.clear();
			path.push_back(i);
			get_path(i,j);
			path.push_back(j);
			path.push_back(k);
		}
    ...
}

结合图示即可理解:imgimgimg

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

const int N=105;

int ans,n,m,d[N][N],a[N][N],pos[N][N];
deque<int>path;

void get_path(int u,int v)
{
	if(!pos[u][v]) return;
	get_path(u,pos[u][v]);//找在中转点之前的
	path.push_back(pos[u][v]);//加入中转
	get_path(pos[u][v],v);//找在中转点之后的
}

int main()
{
	memset(d,0xf,sizeof(d));
	memset(a,0xf,sizeof(a));
	ans=a[0][0];
	
	
	scanf("%d %d",&n,&m);
	for(int i=1,u,v,w;i<=m;++i)
	{
		scanf("%d %d %d",&u,&v,&w);
		d[u][v]=a[u][v]=d[v][u]=a[v][u]=w;
	}
	
	for(int k=1;k<=n;++k)
	{
		for(int i=1;i<k;++i)
		for(int j=i+1;j<k;++j)
		if(d[i][j]+a[j][k]+a[k][i]<ans)
		{
			ans=d[i][j]+a[j][k]+a[k][i];
			path.clear();
			path.push_back(i);
			get_path(i,j);
			path.push_back(j);
			path.push_back(k);
		}
	
		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(ans==d[0][0]) printf("No solution.");
	else
	{
		for(int i=0;i<path.size();++i)
		printf("%d ",path[i]);
	}
}
牛站

给定一张由T条边构成的无向图,点的编号为1~1000之间的整数。

求从起点S到终点E恰好经过N条边(可以重复经过)的最短路。

注意: 数据保证一定有解。

输入格式

第1行:包含四个整数N,T,S,E。

第2..T+1行:每行包含三个整数,描述一条边的边长以及构成边的两个点的编号。

输出格式

输出一个整数,表示最短路的长度。

数据范围

2≤T≤1002≤T≤100,
2≤N≤1062≤N≤106

输入样例:

2 6 6 4
11 4 6
4 4 8
8 4 9
6 6 8
2 6 9
3 8 9

输出样例:

10

#1:采用\(Floyed\)算出任意两点的距离,采用类似于快速幂的方法快速得到答案

#2:当将答案从d数组加到a数组时,不能直接\(a[i][j]+=d[i][j]\),而是应该枚举中转节点,\(a[i][j]=(1<=K<=n)min(a[i][k]+d[k][j])\)

因为i到j正好走2now+x条边时,最小边不一定是从i到j走2now条边再从i到j走x条边(这样看真的就逻辑奇特,但还是写一下证明),可能是i到k走了2now条边,k到j走了x条边。image-20200514173708759

#3:a初始为正无穷,但\(a[i][i]=0\)

#4:\(d[u][v]=d[v][u]=min(d[u][v],e[i].w)\),防止同一条边多次添加(不过本题数据并未出现)

#include<bits/stdc++.h>
using namespace std;
#define INF 0x7ffffffffff
const int T=205;
int n,t,st,ed,s[T],d[T][T],a[T][T],ls[T][T];
struct e_
{
	int u,v,w;
}e[T];

void init()
{
	memset(d,0x3f,sizeof(d));
 	memset(a,0x3f,sizeof(a));
	scanf("%d %d %d %d",&n,&t,&st,&ed);

	for(int i=1,u,v,w;i<=t;++i)
	{
		scanf("%d %d %d",&w,&u,&v);
		s[++s[0]]=u;
		s[++s[0]]=v;
		e[i]=(e_){u,v,w};
	}
	sort(s+1,s+s[0]+1);
	s[0]=unique(s+1,s+s[0]+1)-s-1;

	st=lower_bound(s+1,s+s[0]+1,st)-s;
	ed=lower_bound(s+1,s+s[0]+1,ed)-s;

	for(int i=1;i<=s[0];++i) a[i][i]=0;
	for(int i=1,u,v;i<=t;++i)
	{
		u=lower_bound(s+1,s+s[0]+1,e[i].u)-s;
		v=lower_bound(s+1,s+s[0]+1,e[i].v)-s;
		d[u][v]=d[v][u]=min(d[u][v],e[i].w);
	}
}
void floyed()
{
	for(;n;n>>=1)
	{

		if(n&1)
		{
			memcpy(ls,a,sizeof(a));
			memset(a,0x3f,sizeof(a));

			for(int k=1;k<=s[0];++k)
			for(int i=1;i<=s[0];++i)
			for(int j=1;j<=s[0];++j)
			a[i][j]=min(a[i][j],ls[i][k]+d[k][j]);

		}

	 	memcpy(ls,d,sizeof(d));
		memset(d,0x3f,sizeof(d));

		for(int k=1;k<=s[0];++k)
		for(int i=1;i<=s[0];++i)
		for(int j=1;j<=s[0];++j)
		d[i][j]=min(d[i][j],ls[i][k]+ls[k][j]);


	}
}
int main()
{

	init();
	floyed();
	printf("%d",a[st][ed]);
}

第二最短路

所有路径中第二短的那条路。

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

const int N=5e3+5,M=1e5+5;
int n,m,te,tail[N],d1[N],d2[N];
bool vis[N];
struct e_
{
	int v,w,pre;
}e[M*2];
inline void add(int u,int v,int w)
{
	e[++te]=(e_){v,w,tail[u]};
	tail[u]=te;
}

void spfa()
{	
	deque<int>q;
	q.push_back(1);
	
	memset(d1,0x3f,sizeof(d1));
	memset(d2,0x3f,sizeof(d2));
	d1[1]=0;
	
	while(q.size())
	{
		int u=q.front();
		q.pop_front();
		vis[u]=0;
		for(int i=tail[u];i;i=e[i].pre)
		{
			int v=e[i].v,w=e[i].w,ff=0;
			int val1=d1[u]+w,val2=d2[u]+w;
			if (d1[v]>val1)
			{
				d2[v]=d1[v];
				d1[v]=val1;
				ff=1;
			}
			if (d1[v]==val1&&d2[v]>val2)
			{
				d2[v]=val2;
				ff=1;	
			}
			if (d1[v]<val1&&d2[v]>val1)
			{
				d2[v]=val1;
				ff=1;
			}
			if(ff&&!vis[v])
			{
				vis[v]=1;
				
				if(q.empty()||d1[v]>d1[q.front()]) q.push_back(v);
				else q.push_front(v);
 			}
		}
	}
}
int main()
{
	scanf("%d %d",&n,&m);
	
	for(int i=1,u,v,w;i<=m;++i)
	{
		scanf("%d %d %d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	
	spfa();
	
	printf("%d",d2[n]);
}

最短路计数

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

const int N=1e5+5,M=2e5+5,p=1e5+3;
int n,m,te,tail[N],d[N],cnt[N];
bool vis[N];
struct e_
{
	int v,pre;
}e[M*2];

inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};
	tail[u]=te;
}
void spfa()
{	
	memset(d,0x3f,sizeof(d));
	
	deque<int>q;
	q.push_back(1);
	d[1]=0;
	cnt[1]=1;
	
	while(q.size())
	{
		int u=q.front();
		q.pop_front();
		vis[u]=0;
		
		for(int i=tail[u];i;i=e[i].pre)
		{
			int v=e[i].v;
			if(d[v]==d[u]+1) cnt[v]=(cnt[v]+cnt[u])%p;
			else 
			if(d[v]>d[u]+1)
			{
				d[v]=d[u]+1;
				cnt[v]=cnt[u];
				
				if(!vis[v])
				{
					vis[v]=1;
					if(q.empty()||d[v]>d[q.front()]) q.push_back(v);
					else q.push_front(v);
				}
			}
		}
	}
}
int main()
{
	scanf("%d %d",&n,&m);
	
	for(int i=1,u,v;i<=m;++i)
	{
		scanf("%d %d",&u,&v);
		add(u,v);
		add(v,u);
	}
	
	spfa();
	
	for(int i=1;i<=n;++i) printf("%d\n",cnt[i]);
}

第k短路

//模板针对有向图喔 
#include<bits/stdc++.h>
using namespace std;

const int N=1e4+5,M=255;
int n,m,k,st,ed;
int dis[N];
int te,v[M],w[M],pre[M],tail[N];
int fv[M],fw[M],fpre[M],ftail[N];
bool vis[N];
struct node
{
	int dot,val;
	friend bool operator<(node a,node b)
	{
		return a.val+dis[a.dot]>b.val+dis[b.dot];
	}
};

inline void add(int U,int V,int W)
{
	te++;
	v[te]=V;w[te]=W;pre[te]=tail[U];tail[U]=te;
	fv[te]=U;fw[te]=W;fpre[te]=ftail[V];ftail[V]=te;
}

void dij()
{
	priority_queue<pair<int,int> >q;
	
	memset(dis,0x3f,sizeof(dis));
	
	dis[ed]=0;
	q.push({-dis[ed],ed});
	while(!q.empty())
	{
		int u=q.top().second;
		 
		q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		
		for(int i=ftail[u];i;i=fpre[i])
		if(dis[fv[i]]>dis[u]+fw[i])
		{
			dis[fv[i]]=dis[u]+fw[i];
			q.push({-dis[fv[i]],fv[i]});
		}
	}
}

int bfs()
{
	priority_queue<node>q;
	
	q.push({st,0});
	
	while(!q.empty())
	{
		int u=q.top().dot,d=q.top().val;
		q.pop();
		
		if(u==ed&&!--k) return d;
		
		for(int i=tail[u];i;i=pre[i])
		q.push({v[i],d+w[i]});
	}
	
	return -1;
}

int main()
{
	scanf("%d %d %d %d %d",&n,&m,&st,&ed,&k);
	for(int i=1,u,v,w;i<=m;++i) scanf("%d %d %d",&u,&v,&w),add(u,v,w);
	
	dij();//预处理一下到终点的距离~
//	for(int i=1;i<=n;++i) cout<<i<<" "<<dis[i]<<"\n"; 
	
	printf("%d",bfs());
}

树上任意一点过固定的K个点的最短路

P6419 Kamp

题目描述

一颗树 n个点,n-1条边,经过每条边都要花费一定的时间,任意两个点都是联通的。

有 K个人(分布在 K个不同的点)要集中到一个点举行聚会。

聚会结束后需要一辆车从举行聚会的这点出发,把这 K个人分别送回去。

请你回答,对于 i=1∼n,如果在第 i个点举行聚会,司机最少需要多少时间把 K个人都送回家。

输入格式

第一行两个整数 n,K 。

接下来 n-1行,每行三个数 x,y,z 表示 x到 y之间有一条需要花费 z时间的边。

接下来 K 行,每行一个数,表示 K个人的分布。

输出格式

输出 n个数。

第 i行的数表示:如果在第 i个点举行聚会,司机需要的最少时间。

输入输出样例

输入 #1复制

7 2
1 2 4
1 3 1
2 5 1
2 4 2
4 7 3
4 6 2
3
7

输出 #1复制

11
15
10
13
16
15
10

输入 #2复制

5 2
2 5 1
2 4 1
1 2 2
1 3 2
4
5

输出 #2复制

5
3
7
2
2

说明/提示

数据规模与约定

  • 对于 50% 的数据,保证 n≤2×103

  • 对于 100%100% 的数据,1≤k≤n≤5×105,1≤x,y≤n,1≤z≤108

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int N=5e5+5;
    int n,K,siz[N],son[N];
    ll d[N],ans[N],len1[N],len2[N];
    vector<int>vv[N];
    vector<ll>ww[N];
    
    void dfs1(int u,int fa)
    {
    	for(int i=0;i<vv[u].size();++i)
    	{
    		int v=vv[u][i];ll w=ww[u][i];
    		if(v==fa) continue;
    		
    		dfs1(v,u);
    		
    		if(siz[v])
    		{
    			d[u]+=d[v]+w*2;siz[u]+=siz[v];
    			
    			ll V=len1[v]+w;
    			if(V>len1[u]) len2[u]=len1[u],len1[u]=V,son[u]=v;
    			else if(V>len2[u]) len2[u]=V;
    		}
    	}
    }
    void dfs2(int u,int fa)
    {
    	for(int i=0;i<vv[u].size();++i)
    	{
    		int v=vv[u][i];ll w=ww[u][i];
    		
    		if(v==fa) continue;
    		
    		if(siz[v]==0) ans[v]=ans[u]+w*2,len1[v]=len1[u]+w;
    		else if(siz[v]==K) ans[v]=d[v];
    		else
    		{
    			ans[v]=ans[u];
    			
    			int F;ll V;
    		
    			if(v!=son[u]) V=len1[u]+w,F=u;
    			else V=len2[u]+w,F=1;
    			
    			if(V>len1[v]) len2[v]=len1[v],len1[v]=V,son[v]=u;
    			else if(V>len2[v]) len2[v]=V;
    		}
    		
    		dfs2(v,u);
    	}
    }
    int main()
    {
    	scanf("%d %d",&n,&K);
    	for(int i=1,u,v,w;i<n;++i)
    	{
    		scanf("%d %d %d",&u,&v,&w);
    		vv[u].push_back(v);ww[u].push_back((ll)w);
    		vv[v].push_back(u);ww[v].push_back((ll)w);
    	}
    	for(int i=1,x;i<=K;++i) scanf("%d",&x),siz[x]++;
    	
    	dfs1(1,0);
    	ans[1]=d[1];
    	dfs2(1,0);
    //	for(int i=1;i<=n;++i) cout<<siz[i]<<" "<<d[i]<<" "<<len1[i]<<" "<<len2[i]<<"\n";
    	for(int i=1;i<=n;++i) printf("%lld\n",ans[i]-len1[i]);
    } 
    
posted @ 2020-10-23 19:46  林生。  阅读(90)  评论(0)    收藏  举报