关于P2685
  • 板块题目总版
  • 楼主LoveMe_
  • 当前回复2
  • 已保存回复2
  • 发布时间2023/8/2 17:00
  • 上次更新2023/11/3 06:19:32
查看原帖
关于P2685
764011
LoveMe_楼主2023/8/2 17:00

这道题正解思路是并查集+线段树枚举每一条边,但是本人用了另一种思路,然后不出意外的WA了。

但是我并没有找出我思路中不对的地方,我的代码对于示例数据和自己尝试的一些小数据也都能输出正确的结果: (解释在代码下面)

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int n,m;
struct ed{
	int v,w,next;
}e[400001];
int cnt,head[100001];

void add(int u,int v,int w){
	e[++cnt].next = head[u];
	e[cnt].v = v;
	e[cnt].w = w;
	head[u] = cnt;
	return;
}
int dis[100001][3];
int vis[100001];
int f[100001]; 
int inf = (1<<30)+(1<<29);
struct node{
	int v,dis,from,to;
	const bool operator>(const node b)const{
		return dis>b.dis;
	}
};
int from[100001];
priority_queue<node,vector<node>,greater<node> >Q; 
void dij(){
//	priority_queue<node,vector<node>,greater<node> >Q; 
	for(int i = 1;i<=n;i++)dis[i][1] = dis[i][2] = inf;
	dis[1][1] = dis[1][2] = 0;
	Q.push((node){1,0,0,0});
	while(!Q.empty()){
		node c = Q.top();
		Q.pop();
		if(vis[c.v])continue;
		vis[c.v] = 1;
		from[c.v] = c.from;
		for(int i = head[c.v];i;i = e[i].next){
			if(c.dis+e[i].w<=dis[e[i].v][1]){
				dis[e[i].v][2] = dis[e[i].v][1];
			//	from[e[i].v][2] = from[e[i].v][1];
				dis[e[i].v][1] = c.dis+e[i].w;
				
				Q.push((node){e[i].v,dis[e[i].v][1],i,0});
			}else if(c.dis+e[i].w<dis[e[i].v][2]){
				dis[e[i].v][2] = c.dis+e[i].w;
			}
		}
		
	}
	return;
}
int dis2[100001];
int vis2[100001];
int to[100001];
int to2[100001];

void dij2(){
	while(!Q.empty())Q.pop();
	for(int i = 1;i<=n;i++)dis2[i]= inf;
	dis2[n]= 0;
	Q.push((node){n,0,0,0});
	while(!Q.empty()){
		node c = Q.top();
		Q.pop();
		if(vis2[c.v])continue;
		vis2[c.v] = 1;
		to[c.v] = c.from;
		to2[c.v] = c.to;
		for(int i = head[c.v];i;i = e[i].next){
			if(dis2[e[i].v]>c.dis+e[i].w){
				dis2[e[i].v] = c.dis+e[i].w;
			
				Q.push((node){e[i].v,dis2[e[i].v],i,c.v});
			}
		}
		
	}
	return;
}
int ans1,ans2;
int si[500001];
int a[400001];
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i<=m;i++){
		int te1,te2,te3;
		scanf("%d%d%d",&te1,&te2,&te3);
		add(te1,te2,te3);
		si[cnt] = cnt+1;
		add(te2,te1,te3);
		si[cnt] = cnt-1;
	
	}
	dij();
	dij2();
//	for(int i = 1;i<=n;i++){
//		cout << i <<':'<< dis[i][1]<< ' '<< dis[i][2]<<' '<<dis2[i]<< endl;
////	cout<< i <<':'<< from[i]<< endl;
//	}
	for(int i = 1;i;i = to2[i]){
		if(i==1)continue;
		if(dis[i][2]+dis2[i]>ans1){
			ans1 = dis[i][2]+dis2[i];
		//	ans2 = 1;
		}
	}
	if(ans1 == dis[n][1]){
		cout << ans1 << ' '<< m << endl;
		return 0;
	}
	for(int i = 1;i;i = to2[i]){
		if(i==1)continue;
		if(dis[i][2]+dis2[i]==ans1){
			ans2++;
		}
	}
	cout << ans1 << ' '<< ans2 << endl;
	return 0;
}

像是from、to这些写的很乱,这是由于统计答案第二部分即ans2时我换了几个方法统计,算是历史遗留问题,各位可以忽略不看。只需要记住to2[i]代表从n到1(不是从1到n!)最短路径上点i的上一个点是to2[i]

首先,dis[i][1]显然代表由1到i的最短距离。dis[i][2]代表了不经过1-i最短路径最后一边时从1到i的距离。也就是说,假设由1到i的最短路径为1-2-3-4-....-j-i,那么dis[i][2]就是删去j-i之间的边(只删去j-i之间最短的边,注意这道题目中两点可能有多条边)之后,从1到i的最短路径。dij()方法负责计算dis数组。

显然,假设原本由1-n的最短路径上有一条边x-y,那么dis[y][2]便代表了删去x-y边(boss驻守在x-y边上)之后从1到达y的最短路径。此时只需要从y再按原来的路径走到n即可。dis2[i]表示点i到点n的最短距离,dis2由dij2()计算(当然这是完全没必要的,完全可以直接dis[n][1]-dis[i][1]得到,是我脑抽了)那么按照这种逻辑,只需要枚举1-n最短路径上的所有点i(1除外),计算dis[i][2]+dis2[i],在所有点i之中这个值的最大值就是ans1,答案的第一部分。

在不考虑有两条完全不重合的1-n最短路径的情况(在该种情况下断掉任意一条边都不会影响1-n最短距离)下,ans2,答案的第二部分理应就是满足dis[i][2]+dis2[i]==ans1的点i的数量。

如果有一个点i满足dis[i][1]==dis[i][2],那么可以知道的是断掉这个点的上一条边完全没有意义,同时又不考虑有两条完全不重合的最短路径的情况,那么肯定能找到一条边使得断掉它是“有意义的”,也就是说断掉的不可能是i的上一条边。也就是说,对于这样一个dis[i][1]==dis[i][2]的点i,它不可能满足(排除上述情况)dis[i][2]+dis2[i]==ans1

我的思路写到这里就差不多了。我在写上面解释的时候感觉像是写一篇题解一样——可惜它是错的。由于我找不到这道题的数据,只能求助于各位,希望参与讨论的大家也能有所收获

2023/8/2 17:00
加载中...