luogu P1608 路径统计题解

发布时间 2023-11-10 16:18:36作者: -wryyy-

update 2022.2.17 图炸了

update 2022.8.20 修改了一些错误

step 0 一些建议

做此题前建议先去做 P1144 最短路计数 并且建议使用 Dijkstra 算法去写,原因在 这个帖子这个帖子 也感谢这位大佬的付出。 不然我就要拿SPFA写了。

step 1 坑

这道题有个坑,那就是在这句话上 两个不同的最短路方案要求:路径长度相同(均为最短路长度)且至少有一条边不重合。

我认为应该是所生成的最短路序列不得有重,重复的不加入计数,这里就要求我们去重边了,与 P1144 最短路计数 不同,P1144 是允许序列有重,这里给出一组数据。

输入:

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

所生成的最短路序列为:

1 2 3 (重边中第一条)
1 2 3 (重边中第二条)
1 3

可以看到如果只按字面理解的话那答案应该是:3 3,因为 1-2 这条边有两条。

如果是序列指不重才会是正确答案:3 2。

step 2 计数操作

我们只需要在跑 Dijkstra 算法是加入计数操作就可以了。

如果 $dist[y] > dist[x]+e $ 那么就说明找到了优的路径,将 $y$ 的路径计数用 $x$ 的路径计数覆盖,并将 $y$ 的 $dist$ 值更新,即实施 dist[y] = dist[x]+eans[y]=ans[x] 操作。

如果 $dist[y] = dist[x]+e $ 那么 $y$ 节点就加上 $x$ 节点的计数 ,即实施 ans[y]+=ans[x] 操作。

此处的 $y$ 是 $x$ 所连节点,$e$ 为连接 $x$ 与 $y$ 这条边的权值。

step 3.1-1 迭代器去重

选择用空间换时间的可以跳过这一段,但个人认为有关迭代器的知识大家可以去看看,毕竟以后说不定要用到?

有关于迭代器(iterator),这是一个类似于指针的东西,支持自增(++)和自减(--),这里不再赘述,洛谷日报里有关于这个的介绍。

由于蒟蒻不会写重载运算符,所以直接用了 STL 自带的 pair 类型,它会先以第一关键字进行比较,再以第二关键字进行比较,我们用第一关键字存边的另一端点,用第二关键字存这条边的权值,再使用 sort() 函数进行排序,因为 sort() 默认从小到大,这样可以保证重边都挨在一起,并且第一次出现的某个节点所代表的边一定是所有其代表的边中权值最小的,这样我们便可以执行删除操作,将在其后并与其编号相同的边全部删除。

代码:

for(int i=1;i<=n;i++){
		sort(a[i].begin(),a[i].end());
		ls=INT_MAX;
		for(basic_string <pair<int,int> >::iterator it=a[i].begin();it!=a[i].end();++it){
			ls3=*it;
			if(ls3.first!=ls)ls=ls3.first;
			else {//若是相同则删除,并将迭代器--
				a[i].erase(it);
				--it;
			}
		}
	}

step 3.1-2 Code

话不多说,代码实现:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/priority_queue.hpp>
#define ll  long long
#define ull unsigned long long
using namespace std;
using namespace __gnu_pbds;
inline ll read(){
   ll s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
   return s*w;
}
inline void print(ll ch){
	if (ch<0)ch=-ch,putchar('-');
	if (ch>9)print(ch/10);
	putchar(ch%10+'0');
}
int n,m,s;
int dist[2010],ans[2010];
bool v[2010];
__gnu_pbds::priority_queue<pair<int,int>,greater<pair<int,int> >, pairing_heap_tag > w;//pbds 中的小根堆 
basic_string <pair<int,int> > a[2010];//一个和 vector 差不多的容器
void dij(){//堆优化 Dijkstra
	pair<int,int> x1;
	int x,y,e;
	while(!w.empty()){
		x1=w.top();
		x=x1.second;
		w.pop();
        if(v[x])continue;
		v[x]=1;
		for(int i=0;i<a[x].size();i++){
			y=a[x][i].first;
			e=a[x][i].second;
			if(dist[y]>dist[x]+e){
				dist[y]=dist[x]+e;
				ans[y]=ans[x];//覆盖 
				if(!v[y]){
					w.push(make_pair(dist[y],y));	
				}
			}
			else if(dist[y]==dist[x]+e)ans[y]+=ans[x];//累加 
		}
	}
}
int main()
{
	n=read(),m=read(),s=1;
	int ls,ls1,ls2;
	pair<int,int> ls3;
	for(int i=0;i<m;i++){
		ls=read(),ls1=read(),ls2=read();
		a[ls]+=make_pair(ls1,ls2);//basic_string专属添加元素,理解成 push_back() 即可 
	}
	for(int i=1;i<=n;i++){//这里需要熟悉STL中迭代器的使用 
		sort(a[i].begin(),a[i].end());
		ls=INT_MAX;
		for(basic_string <pair<int,int> >::iterator it=a[i].begin();it!=a[i].end();++it){
			ls3=*it;
			if(ls3.first!=ls)ls=ls3.first;
			else {
				a[i].erase(it);
				--it;
			}
		}
	}
	for(int i=1;i<=n;i++){
		dist[i]=INT_MAX;
	}
	dist[s]=0;
	w.push(make_pair(dist[s],s));
	ans[s]=1;
	dij();
	if(dist[n]==INT_MAX){//如果等于原数值则输出 No answer 
		cout<<"No answer";
		return 0;
	}
	print(dist[n]);
	putchar(32);
	print(ans[n]);
	return 0;
}

运行时间 basic_string

运行时间 vector

在此补充一句,在插入 1e8 以上级别数据时。 basic_string 将会明显慢于 vector 。在开启 O2 的情况下插入 3e8 的数据 basic_string 会被卡到 5000ms 及以上,无论是使用 +=X 或者是直接使用 push_back ,而 vector 若是使用 push_back 会稳定在 4000ms 左右,使用 emplace_back 更是能稳定在 3500ms 左右。但一般的题目并不会使用 1e8 及以上如此离谱的数据,故此我又测试了百万即 1e6 级别的插入,在此时二者的差别也不大了,不开 O2 都是稳定在 250ms 左右,开 O2 二者都稳定在 30ms ,所以在不那么离谱的数据情况下两者都是能选用的。而且 baisc_string += 插入还挺诱人的

综上所述,数据不离谱时按自己喜好选择,比较离谱的话还是用 vector + emplace_back 吧。在以后我可能会在博客对二者进行更细致的讨论。也可能会咕咕咕

step 3.2-1 用空间换时间

这样写就跟写邻接矩阵差不多了,但是或许可以使用 short 类型,因为题目中 $ 1\le C \le 10 $ 而在输入的时候不需要加或者乘,short 类型是完全够用的,这里为了方便还是使用了 int 类型。

首先我们要进行初始化,其实用 11 去初始化就好了,不需要使用到 32767 或者是 INT_MAX ,这里建议使用双重 for 循环而不是 memset() 用了 memset() 会稍微慢点,在开了 O2 后对比会比较明显,不过在 $n$ 较大时这俩差别不大。

接下来就和写邻接矩阵一样了,如果 $ qc_{r1,r2} \le r3 $ 那么就跳过循环,在代码中,我们使用这个语句:

if(qc[ls][ls1]<=ls2)continue;

step 3.2-2 Code

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/priority_queue.hpp>
#define ll  long long
#define ull unsigned long long
using namespace std;
using namespace __gnu_pbds;//以上请忽略 
inline ll read(){//快读 
   ll s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
   return s*w;
}
inline void print(ll ch){//快输 
	if (ch<0)ch=-ch,putchar('-');
	if (ch>9)print(ch/10);
	putchar(ch%10+'0');
}
int n,m,s;
int dist[2010],ans[2010],qc[2010][2010];
bool v[2010];
__gnu_pbds::priority_queue<pair<int,int>,greater<pair<int,int> >, pairing_heap_tag > w;//pbds 中的小根堆 
basic_string <pair<int,int> > a[2010];//一个和 vector 差不多的容器,但似乎更快 
void dij(){//堆优化 Dijkstra
	pair<int,int> x1;
	int x,y,e;
	while(!w.empty()){
		x1=w.top();
		x=x1.second;
		w.pop();
        if(v[x])continue;
		v[x]=1;
		for(int i=0;i<a[x].size();i++){
			y=a[x][i].first;
			e=a[x][i].second;
			if(dist[y]>dist[x]+e){
				dist[y]=dist[x]+e;
				ans[y]=ans[x];//覆盖 
				if(!v[y]){
					w.push(make_pair(dist[y],y));	
				}
			}
			else if(dist[y]==dist[x]+e)ans[y]+=ans[x];//累加 
		}
	}
}
int main()
{
	n=read(),m=read(),s=1;
	int ls,ls1,ls2;
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			qc[i][j]=INT_MAX;
		}
	}
	pair<int,int> ls3;
	for(int i=0;i<m;i++){
		ls=read(),ls1=read(),ls2=read();
		if(qc[ls][ls1]<=ls2)continue;
		a[ls]+=make_pair(ls1,ls2);//basic_string专属添加元素,理解成 push_back() 即可 
		qc[ls][ls1]=ls2;
	}
	for(int i=1;i<=n;i++){
		dist[i]=INT_MAX;
	}
	dist[s]=0;
	w.push(make_pair(dist[s],s));
	ans[s]=1;
	dij();
	if(dist[n]==INT_MAX){//如果等于原数值则输出 No answer 
		cout<<"No answer";
		return 0;
	}
	print(dist[n]);//输出答案 
	putchar(32);
	print(ans[n]);
	return 0;
}

运行时间 for :

运行时间 memset()

如有错误和不足,欢迎 dalao 们指出 qwq。