poj3613 luogu P2886 [USACO07NOV] Cow Relays G

[USACO07NOV] Cow Relays G

题面翻译

给定一张 \(T\) 条边的无向连通图,求从 \(S\)\(E\) 经过 \(N\) 条边的最短路长度。

输入格式

第一行四个正整数 \(N,T,S,E\) ,意义如题面所示。

接下来 \(T\) 行每行三个正整数 \(w,u,v\) ,分别表示路径的长度,起点和终点。

输出格式

一行一个整数表示图中从 \(S\)\(E\) 经过 \(N\) 条边的最短路长度。

数据范围

对于所有的数据,保证 \(1\le N\le 10^6\)\(2\le T\le 100\)

所有的边保证 \(1\le u,v\le 1000\)\(1\le w\le 1000\)

题目描述

For their physical fitness program, N (2 ≤ N ≤ 1,000,000) cows have decided to run a relay race using the T (2 ≤ T ≤ 100) cow trails throughout the pasture.

Each trail connects two different intersections (1 ≤ I1i ≤ 1,000; 1 ≤ I2i ≤ 1,000), each of which is the termination for at least two trails. The cows know the lengthi of each trail (1 ≤ lengthi ≤ 1,000), the two intersections the trail connects, and they know that no two intersections are directly connected by two different trails. The trails form a structure known mathematically as a graph.

To run the relay, the N cows position themselves at various intersections (some intersections might have more than one cow). They must position themselves properly so that they can hand off the baton cow-by-cow and end up at the proper finishing place.

Write a program to help position the cows. Find the shortest path that connects the starting intersection (S) and the ending intersection (E) and traverses exactly N cow trails.

给出一张无向连通图,求S到E经过k条边的最短路。

输入格式

* Line 1: Four space-separated integers: N, T, S, and E

* Lines 2..T+1: Line i+1 describes trail i with three space-separated integers: lengthi , I1i , and I2i

输出格式

* Line 1: A single integer that is the shortest distance from intersection S to intersection E that traverses exactly N cow trails.

样例 #1

样例输入 #1

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

样例输出 #1

10

边数只有100,但是点数有1000,可以离散化然后用临界矩阵,最多200条边。

但是要怎么样才能计算刚好经过\(n\)条边的最短路呢?
以为我觉得这是一个新的知识点,不知道该怎么说思考过程,我是直接看书的。
其实算是倍增,我们用\(f^k[i][j]\)表示从\(i\)\(j\)经过k条路的最短路
\(k=2时\)\(f^2[i][j]=min(a[i][k]+a[k][j])\)

\(k=4\)时,\(f^4[i][j]=min(f^2[i][k]+f^2[k][j])\)

以此类推,当\(k=r\)时,\(f^r[i][j]=min(f^{r-c}[i][k]+f^{c}[k][j])\)

这其实是一个类似矩阵乘法的过程,对于每个转移,其实相当于是进行了一次乘法,乘法的两边是两个\(f\)数组,表示是经过几条边的最短路,结果的矩阵就是经过更多条边的最短路。

然后就可以用倍增的思想算出来任意数量的答案了。单次乘法的复杂度是\(O(n^3)\),对于\(n\leq 1e6\),只用进行\(log_2(n)\)次即可。
最终复杂度\(O(T^3log(n))\)

这题为什么能用这种方法?

这其实算是一个folyd的特性了,很容易理解为什么上面说的过程是对的,folyd的这种实现形式就是能够很容易的保证这个过程是对的。
但是这种矩阵乘法的方式,需要满足什么条件才可以使用呢?
这题里体现出来的有一个,子问题的重复性,这个其实有一部分是以为folyd是dp,但是这个子问题的重复性不是对普通的folyd的,folyd的模板是不具有这种特殊的重复性的,具有这个性质的是这个改版,还有就是解决方法和处理范围的重复性。这个folyd的处理给我一种用矩阵乘法优化数列来快速计算数列第n项的感觉,很有意思,也很特殊,因为每次计算是完全重复的方法,不能说是和矩阵乘法的过程毫无关系,只能说是一模一样。希望以后能多做到几道这种题目,很,,有意思,让我有一种耳目一新的感觉。大概是我做题少吧。。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
	char c=getchar();int a=0,b=1;
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
int n,t,s,e;
int a[205][205],d[205][205],f[205][205],ans[205][205];
int T[101][5],all[205],tot,cnt;
/*struct aa
{
	int a[501][501];
	aa operator * (const aa &x) const
	{
		aa c;
		memset(c.a,0x3f,sizeof(c.a));
		for(int k=1;k<=n;k++)
		{
			for(int i=1;i<=n;i++)
			{
				for(int j=1;j<=n;j++)
				{
					c.a[i][j]=min(c.a[i][j],a[i][k]+x.a[k][j]);
				}
			}
		}
		return c;
	}
};*/
map<int,int> ma;
int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read(),t=read(),s=read(),e=read();
	for(int i=1;i<=t;i++)
	{
		T[i][3]=read(),T[i][2]=read(),T[i][1]=read();
		all[++tot]=T[i][1],all[++tot]=T[i][2];
	}
	all[++tot]=s,all[++tot]=e;
	sort(all+1,all+1+tot);
	for(int i=1;i<=tot;i++)
	{
		if(ma[all[i]]==0)
		{
			ma[all[i]]=++cnt;
		}
	}
	memset(a,0x3f,sizeof(a));
	for(int i=1;i<=t;i++)
	{
		T[i][1]=ma[T[i][1]],T[i][2]=ma[T[i][2]];
		a[T[i][1]][T[i][2]]=a[T[i][2]][T[i][1]]=T[i][3];
	}
    memcpy(d,a,sizeof(a));
	bool flag=0;
	if(n%2==1)memcpy(ans,a,sizeof(a)),flag=1;
	for(int h=1;h<=log(n)/log(2);h++)
	{
		memset(f,0x3f,sizeof(f));
		for(int k=1;k<=tot;k++)
		{
			for(int i=1;i<=tot;i++)
			{
				for(int j=1;j<=tot;j++)
				{
					f[i][j]=min((ll)d[i][k]+d[k][j],(ll)f[i][j]);
				}
			}
		}
		if(((n>>h)&1)==1)
		{
			if(flag==0)
			{
				memcpy(ans,f,sizeof(f));
				flag=1;
			}
			else
			{
				memset(d,0x3f,sizeof(d));
				for(int k=1;k<=tot;k++)
				{
					for(int i=1;i<=tot;i++)
					{
						for(int j=1;j<=tot;j++)
						{
							d[i][j]=min((ll)f[i][k]+ans[k][j],(ll)d[i][j]);
						}
					}
				}
				memcpy(ans,d,sizeof(d));
			}
		}
		memcpy(d,f,sizeof(f));
	}
	cout<<ans[ma[s]][ma[e]]<<endl;
	return 0;
}

还有一个重载运算符的写法,比我这个写法段一倍

可以去luogu上看题解。

这题还有部分贪心的写法,快很多,是\(O(T^2)\),也是在luogu上有

posted @ 2024-02-29 16:07  HL_ZZP  阅读(7)  评论(0编辑  收藏  举报