最短路杂题

最短路杂题

P1346 电车

\(dijkstra\)板子题 初始方向连\(0\)边 否则连\(1\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define pii pair<int,int>
const int inf = 0x3f3f3f3f;
const int N = 1e2 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , dis[N] , s , t;

vector<pii> e[N] , ans;
void add ( int u , int v , int w ) { e[u].emplace_back(v,w); }

struct node { int dis , id; friend bool operator < (const node &a , const node &b) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ()
{
	memset ( dis , inf , sizeof dis );
	dis[s] = 0; q.push({dis[s],s});
	while ( !q.empty() )
	{
		int u = q.top().id , f = q.top().dis; q.pop();
		if ( f != dis[u] ) continue;
		for ( auto [v,w] : e[u] )
			if ( dis[v] > dis[u] + w )
			{
				dis[v] = dis[u] + w;
				q.push ( {dis[v],v} );
			}
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , s = read() , t = read();
	for ( int i = 1 ; i <= n ; i ++ )
	{
		int num = read();
		for ( int j = 1 ; j <= num ; j ++ )
		{
			if ( j == 1 ) add ( i , read() , 0 );
			else add ( i , read() , 1 );
		}
	}
	dij();
	cout << ( dis[t] == inf ? -1 : dis[t] ) << endl;
	return 0;
}

P1144 最短路计数

除了记录\(dis\)之外 需要记录一个\(ans\)为最短路的个数 转移时如果当前路径和最短路相等 那么累加答案 否则更新最短路

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define pii pair<int,int>
const int inf = 0x3f3f3f3f;
const int mod = 100003;
const int N = 1e6 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , dis[N] , s , t , ans[N];

vector<pii> e[N];
void add ( int u , int v , int w ) { e[u].emplace_back(v,w); }

struct node { int dis , id; friend bool operator < (const node &a , const node &b) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ()
{
	ans[1] = 1;
	memset ( dis , inf , sizeof dis );
	dis[1] = 0; q.push({dis[1],1});
	while ( !q.empty() )
	{
		int u = q.top().id , f = q.top().dis; q.pop();
		if ( f != dis[u] ) continue;
		for ( auto [v,w] : e[u] )
		{
			if ( dis[v] > dis[u] + w )
			{
				dis[v] = dis[u] + w , ans[v] = ans[u];
				q.push ( {dis[v],v} );
			}
			else if ( dis[v] == dis[u] + w ) ans[v] = ( ans[v] + ans[u] ) % mod;
		}
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int u = read() , v = read();
		if ( u == v ) continue;
		add ( u , v , 1 ) , add ( v , u , 1 );
	}
	dij();
	for ( int i = 1 ; i <= n ; i ++ ) cout << ans[i] << endl;
	return 0;
}

P1462 通往奥格瑞玛的道路

二分+最短路

可以知道 边越多 最短路越可能短 具有单调性 那么可以考虑二分答案

二分左端点为\(max(a[1],a[n])\) 右端点为\(max\ a[i](1\le i\le n)\)

那么二分一个最大权值 路途中如果碰到大于这个权值的点就跳过 最后统计到达的血量情况

最开始二分移动的\(l,r\)端点搞反了 尴尬

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
const int inf = 0x3f3f3f3f;
const int mod = 100003;
const int N = 1e6 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , dis[N] , a[N] , l , r , k;

vector<pii> e[N];
void add ( int u , int v , int w ) { e[u].emplace_back(v,w); }

struct node { int dis , id; friend bool operator < (const node &a , const node &b) { return a.dis > b.dis; } };
priority_queue<node> q;

int check ( int x )
{
	memset ( dis , inf , sizeof dis );
	dis[1] = 0; q.push({dis[1],1});
	while ( !q.empty() )
	{
		int u = q.top().id , f = q.top().dis; q.pop();
		if ( f != dis[u] ) continue;
		for ( auto [v,w] : e[u] )
		{
			if ( a[v] > x ) continue;
			if ( dis[v] > dis[u] + w )
			{
				dis[v] = dis[u] + w;
				q.push ( {dis[v],v} );
			}
		}
	}
	return dis[n] <= k;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , k = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , r = max ( r , a[i] );
	for ( int i = 1 , u , v , w ; i <= m ; i ++ ) u = read() , v = read() , w = read() , add ( u , v , w ) , add ( v , u , w );
	if ( !check(r) ) return cout << "AFK" << endl , 0;
	l = max ( a[1] , a[n] );
	while ( l <= r )
	{
		if ( check(mid) ) r = mid - 1;
		else l = mid + 1;
	}
	cout << l << endl;
	return 0;
}

P1522 [USACO2.4] 牛的旅行 Cow Tours

观察到数据范围很小 可以想到\(floyd\)预处理两点间距离

\(naive\)的想法是 合并后的直径一定是\(i\)节点的最远距离+\(j\)节点的最远距离+两点间距离 但这是错的

因为有可能合并后的连通块直径还是原来某一个节点的最远距离

所以我们预处理出所有的连通块内直径即可统计 对于两端点不在同一集合中的边 统计答案如下:

res = min ( res , max ( maxf[i] + maxf[j] + dis(p[i],p[j]) , max ( ans[find(i)] , ans[find(j)] ) ) );

完整代码:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
const int N = 150 + 5;
const int inf = 0x3f3f3f3f;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n;
double ans[N] , res = inf , f[N][N] , maxf[N];

int fa[N];
int find ( int x ) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void merge ( int x , int y ) { int fx = find(x) , fy = find(y); if ( fx != fy ) fa[fx] = fy; }

struct node { int x , y; } p[N];

string s;

inl double dis ( node p , node q ) { return sqrt ( ( p.x - q.x ) * ( p.x - q.x ) + ( p.y - q.y ) * ( p.y - q.y ) ); } 

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
	for ( int i = 1 ; i <= n ; i ++ ) p[i].x = read() , p[i].y = read();
	for ( int i = 1 ; i <= n ; i ++ )
	{
		cin >> s;
		for ( int j = 0 ; j < s.size() ; j ++ )
		{
			if ( i == j + 1 ) f[i][j+1] = 0;
			else if ( s[j] == '1' ) merge ( i , j + 1 ) , f[i][j+1] = dis ( p[i] , p[j+1] );
			else f[i][j+1] = inf;
		}
	}
	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] );
	for ( int i = 1 ; i <= n ; i ++ )
	{
		for ( int j = 1 ; j <= n ; j ++ ) if ( find(i) == find(j) ) maxf[i] = max ( maxf[i] , f[i][j] );
		ans[find(i)] = max ( ans[find(i)] , maxf[i] );
	}
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = i + 1 ; j <= n ; j ++ )
			if ( find(i) != find(j) ) res = min ( res , max ( maxf[i] + maxf[j] + dis(p[i],p[j]) , max ( ans[find(i)] , ans[find(j)] ) ) );
	cout << fixed << setprecision(6) << res << endl;
	return 0;
}

P1119 灾后重建

\(floyd\)好题 需要理解其本质 先加入所有边 再每次将\(a\)值小于等于\(t\)的所有点全部更新一遍即可 注意要特判起始点和终点有直接连边但\(a[u]\)\(a[v]\)中有大于\(t\)的不合法情况

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
const int N = 150 + 5;
const int inf = 0x3f3f3f3f;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , q , a[N] , f[N][N] , now;

void upd ( int k )
{
	for ( int i = 0 ; i < n ; i ++ )
		for ( int j = 0 ; j < n ; j ++ )
			f[i][j] = min ( f[i][j] , f[i][k] + f[k][j] );
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	memset ( f , inf , sizeof f );
	n = read() , m = read();
	for ( int i = 0 ; i < n ; i ++ ) a[i] = read() , f[i][i] = 0;
	for ( int i = 1 , u , v , w ; i <= m ; i ++ ) u = read() , v = read() , w = read() , f[u][v] = f[v][u] = w;
	q = read();
	while ( q -- )
	{
		int u = read() , v = read() , w = read();
		while ( a[now] <= w && now < n ) upd(now++);
		if ( a[u] > w || a[v] > w ) cout << -1 << endl;
		else cout << ( f[u][v] == inf ? -1 : f[u][v] ) << endl;
	}
	return 0;
}

P2149 [SDOI2009] Elaxia的路线

简化题意即为:求一无向图中 两点对之间最短路的最长公共路径

那么考虑对于两点对中的四个点分别跑一次\(dij\) 那么我们可以对可能成为公共路径的边建一个新图

具体为:如果\(f[st][i]+f[j][ed]+dis(i,j)=f[st][ed]\) 同时在第二个图上也满足相应的条件 那么这条边是合法的 此时只需要求图中的最长链即可 选用\(topsort\)

注意 如果两个人面对面走过这条路也是可以的 所以我们需要将第二个图的条件中的\(i,j\)调换一下并建立新图 再拓扑排序一遍 这两遍的最长链取最大值即可

是最优解\(qwq\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
const int N = 1500 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , dis[4][N] , rd[N] , f[N] , ans , s1 , t1 , s2 , t2;

vector<pii> e[N] , e1[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }
inl void add1 ( int u , int v , int w ) { e1[u].eb(v,w); }

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;
void dij ( int k , int s )
{
	memset ( dis[k] , inf , sizeof dis[k] );
	while ( !q.empty() ) q.pop();
	q.push ( { s , dis[k][s] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis[k][u] ) continue;
		for ( auto [v,w] : e[u] )
			if ( dis[k][v] > dis[k][u] + w ) 
			{
				dis[k][v] = dis[k][u] + w;
				q.push ( { v , dis[k][v] } );
			}
	}
}

int que[N] , head , tail;
void tp()
{
	memset ( f , 0 , sizeof f );
	head = 1 , tail = 0;
	for ( int i = 1 ; i <= n ; i ++ ) if ( !rd[i] ) que[++tail] = i;
	while ( head <= tail )
	{
		int u = que[head++];
		for ( auto [v,w] : e1[u] )
		{
			f[v] = max ( f[v] , f[u] + w );
			if ( !--rd[v] ) que[++tail] = v;
		}
	}
	for ( int i = 1 ; i <= n ; i ++ ) ans = max ( ans , f[i] );
}


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , s1 = read() , t1 = read() , s2 = read() , t2 = read();
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int u = read() , v = read() , w = read();
		add ( u , v , w ) , add ( v , u , w );
	}
	dij ( 0 , s1 ) , dij ( 1 , t1 ) , dij ( 2 , s2 ) , dij ( 3 , t2 );
	
	for ( int u = 1 ; u <= n ; u ++ )
		for ( auto [v,w] : e[u] )
			if ( dis[0][u] + dis[1][v] + w == dis[0][t1] )
				if ( dis[2][u] + dis[3][v] + w == dis[2][t2] )
					add1 ( u , v , w ) , rd[v] ++;
	tp();
	
	for ( int i = 1 ; i <= n ; i ++ ) e1[i].clear();
	memset ( rd , 0 , sizeof rd );
	
	for ( int u = 1 ; u <= n ; u ++ )
		for ( auto [v,w] : e[u] )
			if ( dis[0][u] + dis[1][v] + w == dis[0][t1] )
				if ( dis[2][v] + dis[3][u] + w == dis[2][t2] )
					add1 ( v , u , w ) , rd[u] ++;
	tp();
	
	return cout << ans << endl , 0;
}

P1875 佳佳的魔法药水

比较板的想法 考虑记录一个\(dis\)和一个\(cnt\)分别表示最小花费和最小花费的种类数

输入数据中存在\(u=v\)的情况 那么需要判断重边 否则会出现未知错误() 实际上 用邻接矩阵可以避免

更新的时候必须等另一个药已经更新到最佳状态之后再去更新这两瓶药的后置药水

初始状态可以将做出所有药的初始价格赋值给\(dis\)并推进队列里即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
#define int long long 
const int N = 1500 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , dis[N] , cnt[N] , vis[N];

vector<pii> e[N];//u+v=w;
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;
void dij ()
{
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis[u] ) continue;
		vis[u] = 1;
		for ( auto [v,w] : e[u] )
			if ( vis[v] )
			{
				if ( dis[w] > dis[u] + dis[v] ) 
				{
					dis[w] = dis[u] + dis[v];
					cnt[w] = cnt[u] * cnt[v];
					q.push ( { w , dis[w] } );
				}
				else if ( dis[w] == dis[v] + dis[u] ) cnt[w] += cnt[u] * cnt[v];
			}
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read();
	for ( int i = 0 ; i < n ; i ++ ) dis[i] = read() , cnt[i] = 1 , q.push ( { i , dis[i] } );
	int u , v , w;
	while ( cin >> u >> v >> w ) { add ( u , v , w ); if ( u == v ) continue; add ( v , u , w ); }
	dij();
	return cout << dis[0] << ' ' << cnt[0] << endl , 0;
}

P1841 [JSOI2007] 重要的城市

很巧妙的一道题目 过编即\(AC\) 但是思路还是参考了题解

可以用\(floyd\)预处理两点间最短路长度和个数 那么我们枚举一个城市\(k\)和两个点\(i,j\) 如果满足\(f[i][j] = f[i][k] + f[k][j]\)\(cnt[i][j] = cnt[i][k] * cnt[k][j];\)

则说明这个点一定是重要城市 因为如果\(i\)\(k\)\(k\)\(j\)的最短路条数乘积就等于\(i\)\(j\)的最短路条数 那么\(i\)\(j\)点的最短路一定是由这两段组合而来(乘法原理)

所以\(i\)\(j\)的最短路全部经过\(k\)\(k\)点即为特殊节点

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
const int N = 200 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , f[N][N] , cnt[N][N] , vis[N];

vector<pii> e[N];//u+v=w;
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) for ( int j = 1 ; j <= n ; j ++ ) f[i][j] = ( i == j ) ? 0 : inf;
	for ( int i = 1 , u , v , w ; i <= m ; i ++ )
	{
		u = read() , v = read() , w = read();
		f[u][v] = f[v][u] = w;
		cnt[u][v] = cnt[v][u] = 1;
	}
	for ( int k = 1 ; k <= n ; k ++ )
		for ( int i = 1 ; i <= n ; i ++ )
			for ( int j = 1 ; j <= n ; j ++ )
			{
				if ( f[i][j] > f[i][k] + f[k][j] )
				{
					f[i][j] = f[i][k] + f[k][j];
					cnt[i][j] = cnt[i][k] * cnt[k][j];
				}
				else if ( f[i][j] == f[i][k] + f[k][j] ) cnt[i][j] += cnt[i][k] * cnt[k][j];
			}
	int flg = 0;
	for ( int k = 1 ; k <= n ; k ++ )
	{
		for ( int i = 1 ; i <= n ; i ++ )
			for ( int j = 1 ; j <= n ; j ++ )
			{
				if ( i == j || j == k || i == k ) continue;
				if ( f[i][j] == f[i][k] + f[k][j] && cnt[i][j] == cnt[i][k] * cnt[k][j] ) { cout << k << ' '; flg = 1; goto flag; }  
			}
		flag :;
	}
	if ( !flg ) cout << "No important cities." << endl;
	return 0;
}

P2865 [USACO06NOV] Roadblocks G

相当于说是你用\(dij\)更新最短路或者最长路的时候 看一下当前的权值与\(dis1\)\(dis2\)的大小关系(也就相当于将它们排序 不过具体实现的时候可以通过交换权值的方法)

相当于是如果当前节点\(u\)的权值加上\(w\)的权值最小 那么更新\(dis1\) 并交换\(diss\)\(dis1\) 也就是用旧的最小值尝试再去更新\(dis2\)

注意 这两个值哪一个更改了都要把相应的权值推进优先队列中 这样保证了更新的正确性

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , dis1[N] , dis2[N];

vector<pii> e[N];//u+v=w;
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ()
{
	memset ( dis1 , inf , sizeof dis1 ); memset ( dis2 , inf , sizeof dis2 );
	q.push ( { 1 , dis1[1] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff > dis2[u] ) continue;
		for ( auto [v,w] : e[u] )
		{
			int diss = ff + w;
			if ( diss < dis1[v] )
			{
				swap ( dis1[v] , diss );
				q.push ( { v , dis1[v] } );
			}
			if ( dis1[v] < diss && diss < dis2[v] )
			{
				dis2[v] = diss;
				q.push ( { v , dis2[v] } );
			}
		}
	}
}


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = 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 );
	dij();
	cout << dis2[n] << endl;
	return 0;
}

P2829 大逃离

和上一道题相差不多 只是多加了更新节点的限制条件 注意 所有边我们都要加进去 因为可能重边中有更小的路径 但是如果这条边的两端节点重复了 那么不用统计\(deg\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
const int N = 5000 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , dis1[N] , dis2[N] , deg[N] , k;
bool mp[N][N];

vector<pii> e[N];//u+v=w;
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ()
{
	for ( int i = 1 ; i <= n ; i ++ ) dis1[i] = dis2[i] = inf;
	q.push ( { 1 , dis1[1] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff > dis2[u] ) continue;
		for ( auto [v,w] : e[u] )
		{
			int diss = ff + w;
			if ( v != 1 && v != n && deg[v] < k ) continue;  
			if ( diss < dis1[v] ) 
			{
				swap ( dis1[v] , diss );
				q.push ( { v , dis1[v] } );
			}
			if ( dis1[v] < diss && diss < dis2[v] )
			{
				dis2[v] = diss;
				q.push ( { v , dis2[v] } );
			}
		}
	}
}


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , k = 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 ); if ( !mp[u][v] ) deg[u] ++ , deg[v] ++ , mp[u][v]= mp[v][u] = 1; }
	dij();
	return cout << ( dis2[n] == inf ? -1 : dis2[n] ) << endl , 0;
}

P4568 [JLOI2011] 飞行路线

给出两种实现:分层图最短路和直接\(dij\)

分层图(\(327ms\)):直接暴力连边 上下层之间连\(0\)边 表示免费关系 每一层中间正常连边即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , k , s , t , dis[N] , ans = inf;

vector<pii> e[N];//u+v=w;
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ()
{
	memset ( dis , inf , sizeof dis );
	q.push ( { s , dis[s] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis[u] ) continue;
		for ( auto [v,w] : e[u] )
			if ( dis[v] > dis[u] + w )
			{
				dis[v] = dis[u] + w;
				q.push ( { v , dis[v] } );
			}
	}
}


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , k = read() , s = read() , t = read();
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int u = read() , v = read() , w = read();
		add ( u , v , w ) , add ( v , u , w );
		for ( int j = 1 ; j <= k ; j ++ )
		{
			add ( u + j * n , v + j * n , w );
			add ( v + j * n , u + j * n , w );
			add ( u + ( j - 1 ) * n , v + j * n , 0 );
			add ( v + ( j - 1 ) * n , u + j * n , 0 ); 
		}		
	} dij();
	for ( int i = 0 ; i <= k ; i ++ ) ans = min ( ans , dis[i*n+t] );
	return cout << ans << endl , 0;
}

直接\(dij\)(\(95ms\) 更高更快更强):主要是设置了\(f[i][j]\)表示用了\(j\)次免费 当前到了第\(i\)个节点的最小花费

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
const int N = 1e4 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
// #define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , k , s , t , dis[N][11] , ans = inf;

vector<pii> e[N];//u+v=w;
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }

struct node { int id , cnt , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ()
{
	memset ( dis , inf , sizeof dis );
	q.push ( { s , dis[s][0] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis , cnt = q.top().cnt; q.pop();
		if ( ff != dis[u][cnt] ) continue;
		for ( auto [v,w] : e[u] )
		{
			if ( cnt < k && dis[v][cnt+1] > dis[u][cnt] ) dis[v][cnt+1] = dis[u][cnt] , q.push ( { v , cnt + 1 , dis[v][cnt+1] } );
			if ( dis[v][cnt] > dis[u][cnt] + w ) dis[v][cnt] = dis[u][cnt] + w , q.push ( { v , cnt , dis[v][cnt] } );
		}
	}
}


signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , k = read() , s = read() , t = read();
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int u = read() , v = read() , w = read();
		add ( u , v , w ) , add ( v , u , w );
	} dij();
	for ( int i = 0 ; i <= k ; i ++ ) ans = min ( ans , dis[t][i] );
	return cout << ans << endl , 0;
}

P8817 [CSP-S 2022] 假期计划

枚举\(b,c\)点即为本题正解

\(n^2bfs\)处理最短路径(边权为\(1\)的时候优先考虑\(bfs\) 可以压掉一个\(log\))

可以先预处理出对于所有\(v\)点 点权排名前三大的\(u\)点 使得这个\(u\)点可以在\(k\)步之内达到\(1\)点 也可以在\(k\)步之内到\(v\)点(用\(set\)维护)

为什么要前三大?因为我们需要经过四个点 假设我们现在遍历对于\(b\) 排名前三大的\(a\)点 那么\(a\)点在预处理的时候已经天然不等于\(b\)点或者\(1\)点了

那么最坏情况是\(b\)这个\(set\)中的三个点有两个点分别和\(c,d\)重合 那么我们必须维护三个点才能保证一定有解

注意:

  1. "转车"不包括起点和终点 也就是两点间距离小于等于\(k+1\)的值全都是合法的

  2. 在最后循环枚举的时候要注意判断四个点不能有重复的

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
#define mkp make_pair
const int N = 2500 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , k , val[N] , dis[N][N] , ans , vis[N];

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].eb(v,w); }
set<pii> s[N];

int q[10000000] , head , tail;

void solve ( int s )
{
	memset ( dis[s] , inf , sizeof dis[s] );
	memset ( vis , 0 , sizeof vis );
	dis[s][s] = 0 , head = 1 , tail = 0 , q[++tail] = s;
	while ( head <= tail )
	{
		int u = q[head++];
		if ( vis[u] ) continue; vis[u] = 1;
		for ( auto [v,w] : e[u] ) if ( dis[s][v] > dis[s][u] + w ) dis[s][v] = dis[s][u] + 1 , q[++tail] = v;
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , k = read();
	for ( int i = 2 ; i <= n ; i ++ ) val[i] = read();
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int u = read() , v = read();
		add ( u , v , 1 ) , add ( v , u , 1 );
	}
	for ( int i = 1 ; i <= n ; i ++ ) solve(i);
	for ( int i = 2 ; i <= n ; i ++ )
		for ( int j = 2 ; j <= n ; j ++ )
			if ( j != i )
			{
				if ( dis[i][j] <= k + 1 && dis[1][j] <= k + 1 ) s[i].insert(mkp(val[j],j));
				if ( s[i].size() > 3 ) s[i].erase(s[i].begin());
			}
	for ( int b = 2 ; b <= n ; b ++ )
		for ( int c = 2 ; c <= n ; c ++ )
			if ( b != c && dis[b][c] <= k + 1 )
				for ( auto [tmp,a] : s[b] ) 
					if ( a != c )
						for ( auto [tmp,d] : s[c] )
							if ( d != a && d != b )
								ans = max ( ans , val[a] + val[b] + val[c] + val[d] );
	return cout << ans << endl , 0;
}

P1266 速度限制

按照题目要求模拟更新最短路即可

如果这条路没有限制 那么就用上一条路本身的限制 否则用这条路的限制

转移时记录前驱即可 最后递归输出

注意数据范围 数组第二维度要开到\(500\) 否则会导致样例不过

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
#define mkp make_pair
const int N = 150 + 5;
const int inf = 0x3f3f3f3f;
// char buf[1<<24] , *p1 , *p2;
// #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , t , val[N] , ans , vis[N];
double dis[N][505];

struct node { int to , spd , len; };
vector<node> e[N];
inl void add ( int u , int v , int w , int l ) { e[u].eb((node){v,w,l}); }

struct kk { double dis; int id , spd; friend bool operator < ( const kk &a , const kk &b ) { return a.dis > b.dis; } } pre[N][505];
priority_queue<kk> q;

void out ( int now , int spd )
{
	if ( now != 0 ) out(pre[now][spd].id,pre[now][spd].spd);
	cout << now << ' ';
}

void dij ()
{
	for ( int i = 0 ; i < n ; i ++ ) for ( int j = 0 ; j <= 500 ; j ++ ) dis[i][j] = (double)inf , pre[i][j].id = -1;
	q.push ( { dis[0][70] = 0 , 0 , 70 } );
	while ( !q.empty() )
	{
		int u = q.top().id , spdu = q.top().spd; double ff = q.top().dis; q.pop();
		if ( ff != dis[u][spdu] ) continue;
		for ( auto [v,spd,len] : e[u] )
		{
			if ( !spd ) spd = spdu;
			if ( dis[v][spd] > ff + 1.0*len/spd )
			{
				dis[v][spd] = ff + 1.0*len/spd;
				pre[v][spd].id = u , pre[v][spd].spd = spdu;
				q.push ( { dis[v][spd] , v , spd } );
			}
		}
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , t = read();
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int u = read() , v = read() , w = read() , l = read();
		add ( u , v , w , l );
	}
	dij();
	int minspd = inf; double minn = inf;
	for ( int i = 0 ; i <= 500 ; i ++ ) if ( minn > dis[t][i] ) minspd = i , minn = dis[t][i];
	return out(t,minspd) , 0;
}

Greg and Graph

逆向思维 考虑将删点看成加点 那么从后到前加点 每一次都标记所有加上的点 并用\(floyd\)更新 更新的时候如果\(i,j\)合法 那么计入答案

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}
int n , m , a[N] , cnt , f[N][N] , in[N] , ans[N] , q[N];

signed main ()
{
	n = read();
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = 1 ; j <= n ; j ++ )
			f[i][j] = read();
	for ( int i = 1 ; i <= n ; i ++ ) q[i] = read();
	for ( int l = n ; l ; l -- )
	{
		int k = q[l];
		in[k] = 1;
		for ( int i = 1 ; i <= n ; i ++ )
			for ( int j = 1 ; j <= n ; j ++ )
			{
				f[i][j] = min ( f[i][k] + f[k][j] , f[i][j] );
				if ( in[i] && in[j] ) ans[l] += f[i][j]; 
			} 
	}
	for ( int i = 1 ; i <= n ; i ++ )
		printf ( "%lld " , ans[i] );
	return 0;
}

Fight Against Traffic

先从\(s\)点跑\(dij\) 再从\(t\)点跑\(dij\) 只要满足\(dis1[i] + dis2[j] + 1 \ge dis1[t]\)且反向边也满足上式 那么这一对点是合法的(在\(f[i][j]=f[j][i]=0\)的前提下)

这样做的正确性来自 如果满足上式 那么这条边一定不是连边后最短路径可能经过的边(如果取等说明一定有另一条最短路和它长度相等) 保证了不会减小最短路

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
#define mkp make_pair
const int N = 1e3 + 5;;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , s , t , dis1[N] , dis2[N] , cnt;
bool f[N][N];

vector<int> e[N];
inl void add ( int u , int v ) { e[u].emplace_back(v); }

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ( int ss , int * dis )
{
	for ( int i = 1 ; i <= n ; i ++ ) dis[i] = inf;
	q.push ( { ss , dis[ss] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis[u] ) continue;
		for ( auto v : e[u] ) if ( dis[v] > dis[u] + 1 ) dis[v] = dis[u] + 1 , q.push ( { v , dis[v] } );
	}
}

string ss;
signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , s = read() , t = read();
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u ) , f[u][v] = f[v][u] = 1;
	dij(s,dis1) , dij(t,dis2);
	for ( int i = 1 ; i < n ; i ++ )
		for ( int j = i + 1 ; j <= n ; j ++ )
			if ( !f[i][j] )
				if ( dis1[i] + dis2[j] + 1 >= dis1[t] && dis1[j] + dis2[i] + 1 >= dis1[t] ) cnt ++;  
	return cout << cnt << endl , 0;
}

Cow and Fields

题目即为 在一个点集中随意挑选两个点\(u,v\) 并在原图中加边\((u,v)\)

有很朴素的\(O(k^2+mlogn)\) \(dij\)预处理+枚举点对统计 但显然超时

考虑优化

可以得出柿子:\(dis1[u]+dis2[v]+1\le dis[n]=dis1[v]+dis2[v]\)

因为你连边越多 最短路径越可能短(这是毋庸置疑的)

回到上面的柿子 可以化简为\(dis1[u]+1\le dis1[v]\) 相当于加完这条边后 损失了\(dis1[v]-dis1[u]-1\)的权值 我们需要尽量减少它的使用

那么策略就是将将点集内的点按照\(dis1\)排序 每次取\(i\)相邻的两个点更新最短路长度

由于最后答案可能大于\(dis[n]\)(假如点集内的所有点都不在最短路上 那么此时的最短路显然还是原来的\(dis[n]\)) 所以要和\(dis[n]\)再比较一次

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
#define mkp make_pair
const int N = 2e5 + 5;;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , k , a[N] , dis1[N] , dis2[N] , maxx;

vector<int> e[N];
inl void add ( int u , int v ) { e[u].emplace_back(v); }

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ( int ss , int * dis )
{
	for ( int i = 1 ; i <= n ; i ++ ) dis[i] = inf;
	q.push ( { ss , dis[ss] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis[u] ) continue;
		for ( auto v : e[u] ) if ( dis[v] > dis[u] + 1 ) dis[v] = dis[u] + 1 , q.push ( { v , dis[v] } );
	}
}

string ss;
signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , k = read();
	for ( int i = 1 ; i <= k ; i ++ ) a[i] = read();
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
	dij(1,dis1) , dij(n,dis2);
	sort ( a + 1 , a + k + 1 , [](const int &x , const int &y) { return dis1[x] < dis1[y]; } );
	for ( int i = 1 ; i < k ; i ++ ) maxx = max ( maxx , dis1[a[i]] + dis2[a[i+1]] + 1 );
	maxx = min ( maxx , dis1[n] );	
	return cout << maxx << endl , 0;
}

Complete The Graph

先将所有边记录下来 边权赋值为\(1\)(因为这是边权能赋的最小值)

跑第一遍\(dij\) 记录\(diff=L-dis[t]\)

\(diff<0\)时输出\(no\) 否则跑第二遍最短路 跑的同时修改边权如下:

if ( check[id] && dis1[v] + diff > dis2[u] + w[id] ) w[id] = dis1[v] + diff - dis2[u];

这样更新的正确性来自于 如果修改了边权 则可以保证一定可以以一种最短路到达终点 那么可以以修改边权与否判断是否合法

\(ps:\)不修改边权的情况:\(diff<0\)(不合法) \(diff=0\)(合法) \(diff>0\)且最短路上并没有能修改的边(不合法)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define pii pair<int,int>
#define eb emplace_back
#define mkp make_pair
const int N = 1e4 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , L , s , t , u[N] , v[N] , w[N] , check[N] , dis1[N] , dis2[N] , diff;

vector<pii> e[N];
inl void add ( int u , int v , int w ) { e[u].emplace_back(v,w); }

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij1()
{
	for ( int i = 0 ; i < n ; i ++ ) dis2[i] = inf;
	q.push ( { s , dis1[s] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis1[u] ) continue;
		for ( auto [v,id] : e[u] ) if ( dis1[v] > dis1[u] + w[id] ) dis1[v] = dis1[u] + w[id] , q.push ( { v , dis1[v] } );
	}
}

void dij2()
{
	for ( int i = 0 ; i < n ; i ++ ) dis2[i] = inf;
	q.push ( { s , dis2[s] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis2[u] ) continue;
		for ( auto [v,id] : e[u] )
		{
			if ( check[id] && dis1[v] + diff > dis2[u] + w[id] ) w[id] = dis1[v] + diff - dis2[u];
			if ( dis2[v] > dis2[u] + w[id] ) dis2[v] = dis2[u] + w[id] , q.push ( { v , dis2[v] } );
		}
	}
}

string ss;
signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , L = read() , s = read() , t = read();
	for ( int i = 1 ; i <= m ; i ++ )
	{
		u[i] = read() , v[i] = read() ,  w[i] = read();
		if ( !w[i] ) check[i] = w[i] = 1;
		add ( u[i] , v[i] , i ) , add ( v[i] , u[i] , i );
	}
	dij1();
	diff = L - dis1[t];
	if ( diff < 0 ) return cout << "NO" << endl , 0;//case1
	if ( !diff ) goto flag;
	dij2();
	if ( dis2[t] ^ L ) return cout << "NO" << endl , 0;//case3
	else
	{
		flag :;
		cout << "YES" << endl;
		for ( int i = 1 ; i <= m ; i ++ ) cout << u[i] << ' ' << v[i] << ' ' << w[i] << endl;
	}
	return 0;
}

P2685 [TJOI2012] 桥

首先介绍题解中使用的数组:

  • \(dis_1\):从\(1\)点出发达到的最短路
  • \(dis_n\):从\(n\)点出发达到的最短路
  • \(st[i]\):最短路径上第\(i\)个点的编号
  • \(id[i]\):最短路径上编号为\(i\)的点是第几个
  • \(l[u]\):\(1\rightarrow n\)\(1\rightarrow u\)的最短路的最后一个重合点的\(st\)
  • \(r[u]\):\(n\rightarrow 1\)\(n\rightarrow u\)的最短路的最后一个重合点的\(st\)
  • \(res[i]\):删掉第\(i\)条边之后 \(1\rightarrow n\)的最短路长度

首先我们可以预处理出\(dis_1\)\(dis_n\)

那么整体架构就分成了三部:

  1. 求出\(st[i]\)\(id[i]\)

    我们可以以\(1\)为起点 向后搜索 遇到在最短路上的点(判定方法为:\(dis_1[u]+dis(u,v)=dis_1[v]\))就加入\(st[i]\) 同时用\(cnt\)记录途中经过的点数即可

  2. 求出\(l[u]\)\(r[u]\)

    这部分可以用\(bfs\)来实现 对于\(l[u]\)\(1\rightarrow n\)上的每一个点\(u\)顺次出发 将没有搜过 \(id=0\)\(u\)\(1\rightarrow v\)的最短路上的点标记为\(id[u]\)

    img

    \(r[u]\)同理 从\(n\rightarrow 1\)上的每一个点\(u\)顺次出发 将没有搜过 \(id=0\)\(u\)\(1\rightarrow v\)的最短路上的点标记为\(id[u]\)

    img
  3. 求出\(res[i]\)数组(也就是答案数组)

    用线段树维护最短路径上的的每一条边

    要使得\(1\rightarrow n\)的长度变大 一定要删掉处于\(1\rightarrow n\)最短路上的边 那么考虑每一条不在这个最短路上的边\(u\rightarrow v\) 如果删除\(l[u]\)\(r[v]-1\)(这里的\(-1\)是因为你最后线段树维护的是每一条边 \(r[u]\)是点 所以需要获取它前面那条边) 中的任意一条边 那么最短路就有可能经过这条\((u,v)\) 那么此时计入长度为\(dis_1[u]+dis_n[v]+w(u,v)\)

    此时在线段树上尝试修改\([l[u],r[v]-1]\)这些边的最小值 用区间取\(min\)的线段树即可

    最后的叶子节点的答案就是\(res\)

两个\(sb\)错误:

  • 线段树的\(t[p]\)修改的时候写成了\(t[x]\)
  • \(head++\)写成了\(++head\)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define eb emplace_back
#define mkp make_pair

const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}
int n , m , dis1[N] , disn[N] , ans , maxx , l[N] , r[N] , id[N] , res[N];
vector<pii> e[N];
struct kk { int u , v , w; friend bool operator < ( const kk &a , const kk &b ) { return a.u == b.u ? ( a.v == b.v ? a.w < b.w : a.v < b.v ) : a.u < b.u; } };
map<kk,int> mp;
inl void add ( int u , int v , int w ) { e[u].emplace_back(v,w); }

int qq[10000000] , head , tail , sta[10000000] , top;

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;
void dij ( int s , int *dis )
{
	for ( int i = 1 ; i <= n ; i ++ ) dis[i] = inf;
	q.push ( { s , dis[s] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis[u] ) continue;
		for ( auto [v,w] : e[u] ) if ( dis[v] > dis[u] + w ) dis[v] = dis[u] + w , q.push ( { v , dis[v] } );
	}
}

void bfs ( int x , int *dis , int *f )
{
	head = 1 , tail = 0;
	qq[++tail] = sta[x] , f[sta[x]] = x;
	while ( head <= tail )
	{
		int u = qq[head++];
		for ( auto [v,w] : e[u] ) 
			if ( !id[v] && !f[v] && dis[u] + w == dis[v] ) f[v] = x , qq[++tail] = v;		
	}
	
}

struct seg
{
	int t[N<<2];
	void build ( int p , int l , int r )
	{
		t[p] = inf;
		if ( l == r ) return;
		build(lson) , build(rson);
	}
	void upd ( int p , int l , int r , int x , int y , int val )
	{
		if ( x <= l && r <= y ) return t[p] = min ( t[p] , val ) , void();
		if ( x <= mid ) upd ( lson , x , y , val );
		if ( mid + 1 <= y ) upd ( rson , x , y , val );
	}
	void down ( int p , int l , int r )
	{
		if ( l == r ) return res[l] = t[p] , void();
		t[ls] = min ( t[ls] , t[p] ) , t[rs] = min ( t[rs] , t[p] ) , down(lson) , down(rson);
	}
}T;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = 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 );
	dij(1,dis1) , dij(n,disn);
	int u = 1;
	while ( u < n ) { sta[++top] = u , id[u] = top; for ( auto [v,w] : e[u] ) if ( disn[v] + w == disn[u] ) { mp[{u,v,w}] = 1 , u = v; break; } }
	sta[++top] = n , id[n] = top;
	for ( int i = 1 ; i <= top ; i ++ ) bfs ( i , dis1 , l ) , bfs ( i , disn , r );
	top -- , T.build(1,1,top);
	for ( u = 1 ; u <= n ; u ++ )
		for ( auto [v,w] : e[u] )
			if ( !mp[{u,v,w}] && l[u] < r[v] ) T.upd ( 1 , 1 , top , l[u] , r[v] - 1 , dis1[u] + disn[v] + w );
	T.down(1,1,top);
	for ( int i = 1 ; i <= top ; i ++ )
	{
		if ( maxx < res[i] ) maxx = res[i] , ans = 1;
		else if ( maxx == res[i] ) ans ++;
	}
	if ( maxx == dis1[n] ) ans = m;//如果删除每一条边后1到n的最短路都不变
	return cout << maxx << ' ' << ans << endl , 0;
}

P1186 玛丽卡

毒瘤题 虽然和上一道是同一道题 但开\(O2\)才能过 貌似数据强得离谱

\(ps:\)稠密图上朴素\(dij\)和堆优化\(dij\)效率相差不多 两种实现都试过 但是均为\(98pts\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define mid (l+r>>1)
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define eb emplace_back
#define mkp make_pair

const int N = 1e5 + 5;
const int inf = 0x3f3f3f3f;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}
int n , m , dis1[N] , disn[N] , ans , maxx , l[N] , r[N] , id[N] , res[N];
vector<pii> e[N];
struct kk { int u , v , w; friend bool operator < ( const kk &a , const kk &b ) { return a.u == b.u ? ( a.v == b.v ? a.w < b.w : a.v < b.v ) : a.u < b.u; } };
map<kk,int> mp;
inl void add ( int u , int v , int w ) { e[u].emplace_back(v,w); }

int qq[10000000] , head , tail , sta[10000000] , top;

struct node { int id , dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;
void dij ( int s , int *dis )
{
	for ( int i = 1 ; i <= n ; i ++ ) dis[i] = inf;
	q.push ( { s , dis[s] = 0 } );
	while ( !q.empty() )
	{
		int u = q.top().id , ff = q.top().dis; q.pop();
		if ( ff != dis[u] ) continue;
		for ( auto [v,w] : e[u] ) if ( dis[v] > dis[u] + w ) dis[v] = dis[u] + w , q.push ( { v , dis[v] } );
	}
}

void bfs ( int x , int *dis , int *f )
{
	head = 1 , tail = 0;
	qq[++tail] = sta[x] , f[sta[x]] = x;
	while ( head <= tail )
	{
		int u = qq[head++];
		for ( auto [v,w] : e[u] ) 
			if ( !id[v] && !f[v] && dis[u] + w == dis[v] ) f[v] = x , qq[++tail] = v;		
	}
}

struct seg
{
	int t[N<<2];
	void build ( int p , int l , int r )
	{
		t[p] = inf;
		if ( l == r ) return;
		build(lson) , build(rson);
	}
	void upd ( int p , int l , int r , int x , int y , int val )
	{
		if ( x <= l && r <= y ) return t[p] = min ( t[p] , val ) , void();
		if ( x <= mid ) upd ( lson , x , y , val );
		if ( mid + 1 <= y ) upd ( rson , x , y , val );
	}
	void down ( int p , int l , int r )
	{
		if ( l == r ) return res[l] = t[p] , void();
		t[ls] = min ( t[ls] , t[p] ) , t[rs] = min ( t[rs] , t[p] ) , down(lson) , down(rson);
	}
}T;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = 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 );
	dij(1,dis1) , dij(n,disn);
	int u = 1;
	while ( u < n ) { sta[++top] = u , id[u] = top; for ( auto [v,w] : e[u] ) if ( disn[v] + w == disn[u] ) { mp[{u,v,w}] = 1 , u = v; break; } }
	sta[++top] = n , id[n] = top;
	for ( int i = 1 ; i <= top ; i ++ ) bfs ( i , dis1 , l ) , bfs ( i , disn , r );
	top -- , T.build(1,1,top);
	for ( u = 1 ; u <= n ; u ++ )
		for ( auto [v,w] : e[u] )
			if ( !mp[{u,v,w}] && l[u] < r[v] ) T.upd ( 1 , 1 , top , l[u] , r[v] - 1 , dis1[u] + disn[v] + w );
	T.down(1,1,top);
	for ( int i = 1 ; i <= top ; i ++ )
	{
		if ( maxx < res[i] ) maxx = res[i] , ans = 1;
		else if ( maxx == res[i] ) ans ++;
	}
	if ( maxx == dis1[n] ) ans = m;//如果删除每一条边后1到n的最短路都不变
	return cout << maxx << ' ' << ans << endl , 0;
}

P1491 集合位置

本来想采用严格最短路的写法 结果发现是非严格次短路()

这里我们采用删边法 即:删除掉任意一条在最短路上的边 再跑 \(dij\) 统计即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define inl inline
#define eb emplace_back
#define pii pair<int,double>
#define fi first
#define se second
#define mkp make_pair
#define fi first
#define se second
const int N = 500 + 5;
const double inf = 0x3f3f3f3f;
// char buf[1<<24] , *p1 , *p2;
// #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f ;
}

int n , m , pre[N];
double minn = inf , dis[N] , x[N] , y[N];

vector<pii> e[N];
inl void add ( int u , int v , double w ) { e[u].eb(v,w); };

struct node { int id; double dis; friend bool operator < ( const node &a , const node &b ) { return a.dis > b.dis; } };
priority_queue<node> q;

void dij ( int x , int y )
{
	for ( int i = 1 ; i <= n ; i ++ ) dis[i] = inf;
	q.push ( { 1 , dis[1] = 0.0 } );	
	while ( !q.empty() )
	{
		int u = q.top().id; double ff = q.top().dis; q.pop();
		if ( dis[u] != ff ) continue;
		for ( auto [v,w] : e[u] )
		{
			if ( ( u == x && v == y ) || ( u == y && v == x ) ) continue;
			if ( dis[v] > dis[u] + w )
			{
				dis[v] = dis[u] + w;
				if ( x == -1 && y == -1 ) pre[v] = u;
				q.push ( { v , dis[v] } );
			}
		}
	}
}

double dist ( int u , int v ) { return hypot ( x[u] - x[v] , y[u] - y[v] ); }

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) cin >> x[i] >> y[i];
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int u = read() , v = read();
		add ( u , v , dist ( u , v ) );
		add ( v , u , dist ( u , v ) );
	}
	dij ( -1 , -1 );
	for ( int i = n ; i != 1 ; i = pre[i] )
	{
		dij ( pre[i] , i );
		minn = min ( minn , dis[n] );
	}
	if ( minn == inf ) cout << -1 << endl;
	else cout << fixed << setprecision(2) << minn << endl;
	return 0;
}
posted @ 2023-07-30 10:46  Echo_Long  阅读(6)  评论(0编辑  收藏  举报