YbtOJ 「图论」 第3章 最短路径

最短路径(有向图)

floyd

求多源最短路的常用方法 通过枚举中间的分界点来实现 复杂度O(n3)

memset ( f , 0x3f , sizeof ( f ) );
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]);

图的传递闭包

指一个矩阵f, f[i][j]就是其中i,j两个点之间的最短路径

dijkstra

单源最短路 基于贪心思想

【算法】最短路径查找—Dijkstra算法_哔哩哔哩_bilibili

先将初始所有点的距离设置无穷大 起点加入队列 { 然后把队列中里起点最短的点提出来 遍历这个点的所有出边 更新距离目标点的最短距离 一旦更新 就扔到队列中 } 只要队列不为空 就重复括号内的内容 始终每个点只会进出队列一次 只要一个点作为u节点更新过了 这个节点就永远不会作为u节点来使用

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

void dij ( )
{
	memset ( dis , inf , sizeof dis );
	priority_queue <kk> q;
	dis[1] = 0;
	q.push ( kk { dis[1] , 1 } );
	while ( !q.empty() )
	{
		int u = q.top().id , f = q.top().dis; q.pop();
		if ( f != dis[u] ) continue;
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			if ( dis[v] > dis[u] + e[i].w )
			{
				dis[v] = dis[u] + e[i].w;
				q.push ( kk { dis[v] , v } );
			}
		}
	}
}

spfa

单源最短路 但是时间复杂度在毒瘤数据面前可能退化到O(nm) 考场慎用 但是当出现负边的时候 spfa是首选

算法思路:先将初始所有点的距离设置无穷大 用队列来保存待优化的结点 vis数组用于保存这个点是否在队列中 起点加入队列 优化时每次取出队首结点 并将vis[u]改为0 遍历这个节点的所有出边 如果可以更新最短路 就把这条边的终点加入队列中(如果队列中没有这个节点的话)

queue < int > q;
void spfa(int s)
{
    for(int i=1;i<=n;i++) dis[i]=inf;
    q.push(s);
    dis[s]=0;vis[s]=1;
    while(!q.empty())
    {
        int u=q.front();q.pop();vis[u]=0;
        for(int i=head[u];i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(dis[v]>dis[u]+e[i].w)
            {
                dis[v]=dis[u]+e[i].w;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
}

A. 【例题1】单源最短路径

[题目描述]

给定一个n个点,m条有向边的带非负权图,请你计算从s出发,到每个点的距离。

数据保证你能从s出发到任意点。

[输入格式]

第一行为三个正整数n,m,s。接下来n行,每行三个非负整数u,v,w,表示从uv有一条权值为w的有向边。

[输出格式]

输出一行n个空格分隔的非负整数,表示s到每个点的距离。

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define pii pair < int , int >
#define inl inline
#define mkp make_pair
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 5;
inl 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 , dis[N];

int head[N] , cnt;
struct node { int to , nxt , w; } e[N<<1];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w }; head[u] = cnt; }


priority_queue < pii , vector<pii> , greater<pii> > q;
void dij ( int s )
{
	memset ( dis , inf , sizeof dis );
	dis[s] = 0;
	q.push ( mkp(0,s) );
	while ( !q.empty() )
	{
		int u = q.top().se , d = q.top().fi;
		q.pop();
		if ( d != dis[u] ) continue;
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			if ( dis[v] > dis[u] + e[i].w )
			{
				dis[v] = dis[u] + e[i].w;
				q.push ( pii ( dis[v] , v ) );
			}
		}
	}
}

signed main ()
{
	n = read() , m = read() , s = read(); 
	for ( int i = 1 , u , v , w ; i <= m ; i ++ ) u = read() , v = read() , w = read() , add ( u , v , w );
	dij(s);
	for ( int i = 1 ; i <= n ; i ++ ) printf ( "%d " , dis[i] );
	return 0;
}

B. 【例题2】负环判断

[题目描述]

给定一个 n 个点的有向图,请求出图中是否存在从顶点 1 出发能到达的负环。

负环的定义是:一条边权之和为负数的回路。

[输入格式]

本题单测试点有多组测试数据

输入的第一行是一个整数 T,表示测试数据的组数。对于每组数据的格式如下:

第一行有两个整数,分别表示图的点数 n 和接下来给出边信息的条数 m

接下来 m 行,每行三个整数 u,v,w

  • w0,则表示存在一条从 uv 边权为 w 的边,还存在一条从 vu 边权为 w 的边。
  • w<0,则只表示存在一条从 uv 边权为 w 的边。

[输出格式]

对于每组数据,输出一行一个字符串,若所求负环存在,则输出 YES,否则输出 NO

[算法分析]

用spfa 对于每一个节点记录它的入队次数(如果记录松弛次数的话可能会有蜜汁错误) 如果大于n的话 说明有负环

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define pii pair < int , int >
#define inl inline
#define mkp make_pair
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 5;
inl 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;
int head[N] , cnt;
struct node { int to , nxt , w; } e[N];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w }; head[u] = cnt; }

void init()
{
	memset ( head , 0 , sizeof head );
	cnt = 0;
}
queue <int> q;
int dis[N] , in[N] , vis[N] , h , t;
bool spfa ( int s )
{
	memset ( dis , inf , sizeof dis );
	memset ( in , 0 , sizeof in );
	memset ( vis , 0 , sizeof vis );
	q.push(s) , in[s] = 1 , dis[s] = 0;
	while ( !q.empty() )
	{
		int u = q.front(); q.pop(); in[u] = 0;
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			if ( dis[v] > dis[u] + e[i].w )
			{
				dis[v] = dis[u] + e[i].w;
				vis[v] = vis[u] + 1;//到这里的最短路经过的边的个数(也就是松弛次数) 
				if ( vis[v] >= n ) return 1;
				if ( !in[v] ) q.push(v) , in[v] = 1;
			}
		}
	}
	return 0;
}

int main()
{
	int T = read();
	while ( T -- )
	{
		init();
		n = read() , m = read();
		for ( int i = 1 , u , v , w ; i <= m ; i ++ )
		{
			u = read() , v = read() , w = read();
			add ( u , v , w );
			if ( w >= 0 ) add ( v , u , w );
		}
		if ( spfa(1) ) printf ( "YE5\n" );
		else printf ( "N0\n" );
	}
	return 0;
} 

C. 【例题3】最优贸易

[题目描述]

$C n m$ 条道路,每条道路连接这 n个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1条。

C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C 国 n 个城市的标号从 1n,阿龙决定从 1号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

假设 C国有 5个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。

假设 1 n 号城市的水晶球价格分别为 4,3,5,6,1

阿龙可以选择如下一条线路:1->2->3->5,并在 $2 3$ 的价格买入水晶球,在 3号城市以5的价格卖出水晶球,赚取的旅费数为 2。

阿龙也可以选择如下一条线路1->4->5->4->5,并在第$1 51 $的价格买入水晶球,在第 2 次到达4 号城市时以6 的价格卖出水晶球,赚取的旅费数为5

现在给出 $n m$ 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

[输入格式]

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

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

接下来 m 行,每行有$ 3 x,y,z$,每两个整数之间用一个空格隔开。如果 z=1,表示这条道路是城市$ x y z=2x y $之间的双向道路。

[输出格式]

一个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出 0

[算法分析]

分层图最长路经典题目 将每一个点分成三层 第一层的u节点向第二层的u节点连边 边权为负的点权 表示买入水晶球

第二层的u节点向第三层的u节点连边 边权为正的点权 表示卖出水晶球

上下两层之间不连双向边 这样保证了只能买入一次和卖出一次 最终答案就是max(dis[n],dis[3n]) 因为买了不卖肯定是不优的

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define pii pair < int , int >
#define inl inline
#define mkp make_pair
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 1e6 + 5;
inl 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];

int head[N] , cnt;
struct node { int to , nxt , w; } e[N*3];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w }; head[u] = cnt; }

int dis[N] , in[N] , q[N] , t , h;
void spfa ( int s )
{
	memset ( dis , -inf , sizeof dis );
	memset ( in , 0 , sizeof in );
	t = 0 , h = 1;
	q[++t] = s , in[s] = 1 , dis[s] = 0;
	while ( h <= t )
	{
		int u = q[h++]; in[u] = 0;
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			if ( dis[v] < dis[u] + e[i].w )
			{
				dis[v] = dis[u] + e[i].w;
				if ( !in[v] ) { in[v] = 1 , q[++t] = v; }
			}
		}
	}
}

signed main ()
{
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , add ( i , i + n , - a[i] ) , add ( i + n , i + 2 * n , a[i] );
	for ( int i = 1 , u , v , w ; i <= m ; i ++ ) 
	{
		u = read() , v = read() , w = read();
		add ( u , v , 0 ) , add ( u + n , v + n , 0 ) , add ( u + 2 * n , v + 2 * n , 0 );
		if ( w == 2 ) add ( v , u , 0 ) , add ( v + n , u + n , 0 ) , add ( v + 2 * n , u + 2 * n , 0 );
	}
	spfa(1);
	printf ( "%d" , max ( dis[n] , dis[3*n] ) );
	return 0;
}

D. 【例题4】汽车加油

[题目描述]

给定一个 N×N 的方形网格,设其左上角为起点◎,坐标(1,1)X 轴向右为正, Y 轴向下为正,每个方格边长为 1 ,如图所示。

一辆汽车从起点◎出发驶向右下角终点▲,其坐标为 (N,N)

在若干个网格交叉点处,设置了油库,可供汽车在行驶途中加油。汽车在行驶过程中应遵守如下规则:

  1. 汽车只能沿网格边行驶,装满油后能行驶 K 条网格边。出发时汽车已装满油,在起点与终点处不设油库。

  2. 汽车经过一条网格边时,若其 X 坐标或 Y 坐标减小,则应付费用 B ,否则免付费用。

  3. 汽车在行驶过程中遇油库则应加满油并付加油费用 A

  4. 在需要时可在网格点处增设油库,并付增设油库费用 C(不含加油费用A )。

  5. N,K,A,B,C 均为正整数, 且满足约束: 2N100,2K10

设计一个算法,求出汽车从起点出发到达终点所付的最小费用。

[输入格式]

文件的第一行是 N,K,A,B,C 的值。

第二行起是一个N×N01 方阵,每行 N 个值,至 N+1 行结束。

方阵的第 i 行第 j 列处的值为 1 表示在网格交叉点 (i,j) 处设置了一个油库,为 0 时表示未设油库。各行相邻两个数以空格分隔。

[输出格式]

程序运行结束时,输出最小费用。

[算法分析]

分层图最短路 对于每一个点 都有油箱的使用油量 当这个油量为k的时候 说明没油了 如果这个油量为0的时候 说明满油且没使用

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define pii pair < int , int >
#define inl inline
#define mkp make_pair
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 1e6 + 5;
inl 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 , K , A , B , C;

int head[N] , cnt;
struct node { int to , nxt , w; } e[N*2];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w }; head[u] = cnt; }

int dis[N] , in[N] , q[20000000] , t , h , ans = inf;
void spfa ( int s )
{
	memset ( dis , inf , sizeof dis );
	memset ( in , 0 , sizeof in );
	t = 0 , h = 1;
	q[++t] = s , in[s] = 1 , dis[s] = 0;
	while ( h <= t )
	{
		int u = q[h++]; in[u] = 0;
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			if ( dis[v] > dis[u] + e[i].w )
			{
				dis[v] = dis[u] + e[i].w;
				if ( !in[v] ) { in[v] = 1 , q[++t] = v; }
			}
		}
	}
}


int g ( int i , int j , int k ) { return ( i - 1 ) * n + j + k * n * n; }
int check ( int i , int j ) { return 1 <= i && i <= n && 1 <= j && j <= n; }

signed main ()
{
	n = read() , K = read() , A = read() , B = read() , C = read();
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = 1 , op ; j <= n ; j ++ )
		{
			op = read();
			if ( op )//强制加油 只能从0连到1 其他点是不可能向外面连边的
			{
				for ( int k = 1 ; k <= K ; k ++ ) add ( g(i,j,k) , g(i,j,0) , A );
				if ( check ( i + 1 , j ) ) add ( g(i,j,0) , g(i+1,j,1) , 0 );
				if ( check ( i - 1 , j ) ) add ( g(i,j,0) , g(i-1,j,1) , B );
				if ( check ( i , j + 1 ) ) add ( g(i,j,0) , g(i,j+1,1) , 0 );
				if ( check ( i , j - 1 ) ) add ( g(i,j,0) , g(i,j-1,1) , B );
			}
			else
			{
				for ( int k = 1 ; k <= K ; k ++ ) add ( g(i,j,k) , g(i,j,0) , A + C );
				for ( int k = 0 ; k < K ; k ++ ) 
				{
					if ( check ( i + 1 , j ) ) add ( g(i,j,k) , g(i+1,j,k+1) , 0 );
					if ( check ( i - 1 , j ) ) add ( g(i,j,k) , g(i-1,j,k+1) , B );
					if ( check ( i , j + 1 ) ) add ( g(i,j,k) , g(i,j+1,k+1) , 0 );
					if ( check ( i , j - 1 ) ) add ( g(i,j,k) , g(i,j-1,k+1) , B );
				} 
			}
		}
	spfa(g(1,1,0));
	for ( int k = 1 ; k <= K ; k ++ ) ans = min ( ans , dis[g(n,n,k)] );
	printf ( "%d" , ans );
	return 0;
}

E. 1.最小花费

设置dis[i][j]表示到第i个节点 访问了j次状态的最小花费 那么每次将合法的免费/不免费状态都扔进队列中即可

变量定义一定要定义明白 cnt和cntt区分开!!!

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e6 + 5; 
const int inf = 0x3f3f3f3f;
inl 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 dis[N][12] , vis[N][12] , s , t , n , m , kk , minn = inf;

int head[N] , cnt;
struct edge { int to , nxt , w; } e[N];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w } , head[u] = cnt; }

struct node
{
	int pos , d , cnt;
	friend bool operator < ( node a , node b ) { return a.d > b.d; }
};

priority_queue <node> q;

void dij ()
{
	memset ( dis , inf , sizeof dis );
	dis[s][0] = 0;
	q.push ( (node) { s , 0 , 0 } );
	while ( !q.empty() )
	{	
		node temp = q.top(); q.pop();
		int u = temp.pos , cntt = temp.cnt;
		if ( vis[u][cntt] ) continue;
		vis[u][cntt] = 1;
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			
			if ( dis[v][cntt+1] > dis[u][cntt] && cntt < kk )
			{
				dis[v][cntt+1] = dis[u][cntt];
				q.push ( (node) { v , dis[v][cntt+1] , cntt + 1 } );
			}
			if ( dis[v][cntt] > dis[u][cntt] + e[i].w )
			{
				dis[v][cntt] = dis[u][cntt] + e[i].w;
				q.push ( (node) { v , dis[v][cntt] , cntt } );
			}
		}
	}
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , kk = 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 <= kk ; i ++ ) minn = min ( minn , dis[t][i] );
	cout << minn << endl;
	return 0;
}

F. 2.修建道路

维护一个val[i]数组表示1i之间想要连通的最小免费使用次数 那么二分答案再dij判断即可

假做法:二分编号 但因为免费修建是双向边

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define mid ((l+r)>>1)
const int N = 1e6 + 5; 
const int inf = 0x3f3f3f3f;
inl 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 val[N] , n , m , k , l , r = -inf;

int head[N] , cnt;
struct edge { int to , nxt , w; } e[N];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w } , head[u] = cnt; }

int check ( int x )
{
	memset ( val , inf , sizeof val );
	priority_queue <pii> q;	
	val[1] = 0;
	q.push ( mkp ( 0 , 1 ) );
	while ( !q.empty() )
	{
		int u = q.top().se , f = q.top().fi; q.pop();
		if ( f != val[u] ) continue;
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			if ( val[v] > val[u] + ( e[i].w > x ) )
			{
				val[v] = val[u] + ( e[i].w > x );
				q.push ( mkp ( val[v] , v ) );
			}
		}
	}
	return val[n] <= k;
}

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

G. 3.糖果分配

差分约束经典模板

  1. 我们对于aiajb的这样一个关系 化简之后可以得到aiaj+b 那么我们可以从ji连边权为b的边表示这种关系 跑最长路

    无解:有正环即为无解 因为此时ij都可以取得无穷小

  2. 如果是aiajb的关系 那么我们跑最短路

    无解:有负环即为无解

最大值求最短路 最小值求最长路

对于本题而言 选择第一种方式即可

在这里用spfa是过不去的 必须先缩点

对于T=1 那么连(a,b,0)(b,a,0)两条边

T=2 a<b 也就是ba1 那么连(a,b,1)

T=3 ab 那么连(b,a,0)

T=4 ab1 那么连(b,a,1)

T=5 ab 那么连(a,b,0)

一定要注意 每个点的初始值必须为1

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N = 1e6 + 5; 
const int inf = 0x3f3f3f3f;
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , val[N] , ans;

int head[N] , cnt;
int head1[N] , cnt1;
struct node { int to , nxt , w ; } e[N] , e1[N];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w }; head[u] = cnt; }
void add1 ( int u , int v , int w ) { e1[++cnt1] = { v , head1[u] , w }; head1[u] = cnt1; }

int dfn[N] , low[N] , timer;
int scc[N] , id[N] , tot;
int in[N] , sta[100000000] , tp;

void tarjan ( int u )
{
	dfn[u] = low[u] = ++timer;
	sta[++tp] = u , in[u] = 1;
	for ( int i = head[u] ; i ; i = e[i].nxt )
	{
		int v = e[i].to;
		if ( !dfn[v] ) tarjan(v) , low[u] = min ( low[u] , low[v] );
		else if ( in[v] ) low[u] = min ( low[u] , dfn[v] );
	}
	if ( dfn[u] == low[u] )
	{
		tot ++;
		while ( tp )
		{
			int x = sta[tp--];
			id[x] = tot , in[x] = 0 , scc[tot] ++;
			if ( x == u ) break;
		}
	}
}

signed main ()
{
	n = read() , m = read();
	for ( int i = 1 , op , a , b ; i <= m ; i ++ )
	{
		op = read() , a = read() , b = read();
		if ( op == 1 ) add ( a , b , 0 ) , add ( b , a , 0 );
		else if ( op == 2 ) add ( a , b , 1 );
		else if ( op == 3 ) add ( b , a , 0 );
		else if ( op == 4 ) add ( b , a , 1 );
		else add ( a , b , 0 );
	}
	for ( int i = 1 ; i <= n ; i ++ ) if ( !dfn[i] ) tarjan(i);
	for ( int u = 1 ; u <= n ; u ++ )
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			if ( id[u] != id[v] ) add1 ( id[u] , id[v] , e[i].w );
			else if ( e[i].w == 1 ) { cout << "-1" << endl; return 0; }//自己和自己的差为1显然不合法
		}
	for ( int i = 1 ; i <= tot ; i ++ ) val[i] = 1;//
	for ( int u = tot ; u ; u -- )
	{
		ans += val[u] * scc[u];
		for ( int i = head1[u] ; i ; i = e1[i].nxt )
		{
			int v = e1[i].to;
			val[v] = max ( val[v] , val[u] + e1[i].w );
		}
	}
	cout << ans << endl;
	return 0;
}

H. 4.比较大小

floyd维护即可 注意无解情况的判断

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define mid ((l+r)>>1)
const int N = 1e3 + 5; 
const int inf = 0x3f3f3f3f;
inl 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 f[N][N] , n , m , q;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	m = read() , n = read() , q = read();
	for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , f[u][v] = 1;
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = 1 ; j <= n ; j ++ )
			for ( int k = 1 ; k <= n ; k ++ )
				f[i][j] |= f[i][k] & f[k][j];
	for ( int i = 1 ; i <= n ; i ++ ) 
		for ( int j = 1; j <= n ; j ++ )
			if ( i != j && f[i][j] && f[j][i] ) 
			{ cout << "10000words to copy" << endl; return 0; }
	for ( int i = 1 ; i <= q ; i ++ )
	{
		int l = read() , r = read();
		if ( f[l][r] ) cout << "YES" << endl;
		else if ( f[r][l] ) cout << "NO" << endl;
		else cout << "DK" << endl;
	}
	return 0;
}

I. 5.删边问题

考虑二分答案 先将所有边按照价值排序 然后二分一个边的价值 将所有小于这个边的所有边都忽略 跑最短路即可

二分有两种策略:

  1. 二分编号

  2. er

满足二分答案的原因:因为加一个边一定不会让最短路变长 减一个边一定不会让最短路变短

dijkstra永远不要用pair!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 用了就90

二分编号代码:

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define mid ((l+r)>>1)
const int N = 1e6 + 5; 
const int inf = 0x3f3f3f3f;
inl 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 , dis[N];

struct node { int u , v , len , val; } a[N];

int head[N] , cnt;
struct edge { int to , nxt , w , val; } e[N];
void add ( int u , int v , int w , int val ) { e[++cnt] = { v , head[u] , w , val } , head[u] = cnt; }

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

int check ( int x )
{
	memset ( dis , inf , sizeof dis );
	priority_queue <kk> q;
	dis[1] = 0;
	q.push ( kk { dis[1] , 1 } );
	while ( !q.empty() )
	{
		int u = q.top().id , f = q.top().dis; q.pop();
		if ( f != dis[u] ) continue;
		for ( int i = head[u] ; i ; i = e[i].nxt )
		{
			if ( e[i].val <= x ) continue;
			int v = e[i].to;
			if ( dis[v] > dis[u] + e[i].w )
			{
				dis[v] = dis[u] + e[i].w;
				q.push ( kk { dis[v] , v } );
			}
		}
	}
	return dis[n] >= T;//最短路合法 尝试减小最大价值
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , T = read();
	for ( int i = 1 ; i <= m ; i ++ ) a[i].u = read() , a[i].v = read() , a[i].len = read() , a[i].val = read();
	sort ( a + 1 , a + m + 1 , [](const node a , const node b) { return a.val < b.val; } );
	for ( int i = 1 ; i <= m ; i ++ ) add ( a[i].u , a[i].v , a[i].len , i );
	if ( check(0) ) { cout << "-1" << ' ' << dis[n] << endl; return 0; }
	int l = 1 , r = m;
	while ( l <= r )
	{
		if ( check(mid) ) r = mid - 1;
		else l = mid + 1;
	}
	cout << a[l].val << endl;
	return 0;
}

二分权值代码:

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

#define mid ((l+r)>>1)

#define int long long 

const int N = 2e6 + 5; 
const int inf = 0x3f3f3f3f;
inl 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 , l = 0 , r = -inf, dis[N];

int head[N] , cnt;
struct node { int to , nxt , w , val; } e[N];
void add ( int u , int v , int w , int val ) { e[++cnt] = { v , head[u] , w , val }; head[u] = cnt; }

struct kk
{
	int dis , id;
	friend bool operator < ( kk a , kk b ) { return a.dis > b.dis; }
};


int check ( int x )//小于等于这个边权的都删掉 跑最短路
{
	memset ( dis , inf , sizeof dis );
	priority_queue <kk> q;
	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 ( int i = head[u] ; i ; i = e[i].nxt )
		{
			int v = e[i].to;
			if ( e[i].val <= x ) continue;
			if ( dis[v] > dis[u] + e[i].w ) 
			{
				dis[v] = dis[u] + e[i].w;
				q.push ( { dis[v] , v } );
			}
		}
	}
	return dis[n] >= T;//路径长度符合要求 尝试减小权值
}



signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , T = read();
	for ( int i = 1 , u , v , len , val ; i <= m ; i ++ ) u = read() , v = read() , len = read() , val = read() , add ( u , v , len , val ) , r = max ( val , r );
	if ( check(0) ) { cout << -1 << ' ' << dis[n] << endl; return 0; }
	while ( l <= r )
	{
		if ( check(mid) ) r = mid - 1;
		else l = mid + 1;
	}
	cout << l << endl;
	return 0;
}
posted @   Echo_Long  阅读(236)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示