浅谈基础的图算法——最短路算法相关例题讲解(c++)

例题讲解

P4667 [BalticOI 2011 Day1] Switch the Lamp On(01最短路)

题面翻译
题目描述

Casper 正在设计电路。有一种正方形的电路元件,在它的两组相对顶点中,有一组会用导线连接起来,另一组则不会。有 N × M N\times M N×M 个这样的元件,你想将其排列成 N N N 行,每行 M M M 个。 电源连接到板的左上角。灯连接到板的右下角。只有在电源和灯之间有一条电线连接的情况下,灯才会亮着。为了打开灯,任何数量的电路元件都可以转动 90°(两个方向)。

在上面的图片中,灯是关着的。如果右边的第二列的任何一个电路元件被旋转 90°,电源和灯都会连接,灯被打开。现在请你编写一个程序,求出最小需要多少旋转多少电路元件。

输入输出格式
输入格式

输入的第一行包含两个整数 N N N 和 M M M,表示盘子的尺寸。 在以下 N N N 行中,每一行有 M M M 个符号 \/,表示连接对应电路元件对角线的导线的方向。

输出格式:

如果可以打开灯,那么输出只包含一个整数,表示最少转动电路元件的数量。

如果不可能打开灯,输出 NO SOLUTION

题目描述

Casper is designing an electronic circuit on a N × M N \times M N×M rectangular grid plate. There are N × M N \times M N×M square tiles that are aligned to the grid on the plate. Two (out of four) opposite corners of each tile are connected by a wire.

A power source is connected to the top left corner of the plate. A lamp is connected to the bottom right corner of the plate. The lamp is on only if there is a path of wires connecting power source to lamp. In order to switch the lamp on, any number of tiles can be turned by 90° (in both directions).

In the picture above the lamp is off. If any one of the tiles in the second column from the right is turned by 90° , power source and lamp get connected, and the lamp is on.

Write a program to find out the minimal number of tiles that have to be turned by 90° to switch the lamp on.

输入格式

The first line of input contains two integer numbers N N N and M M M, the dimensions of the plate. In each of the following N N N lines there are M M M symbols -- either \ or / -- which indicate the direction of the wire connecting the opposite vertices of the corresponding tile.

输出格式

There must be exactly one line of output. If it is possible to switch the lamp on, this line must contain only one integer number: the minimal number of tiles that have to be turned to switch on the lamp. If it is not possible, output the string: NO SOLUTION

样例 #1
样例输入 #1
3 5
\\/\\
\\///
/\\\\
样例输出 #1
1
提示

对于 40 % 40\% 40% 的数据, 1 ≤ N ≤ 4 1 \le N \le 4 1≤N≤4, 1 ≤ M ≤ 5 1 \le M \le 5 1≤M≤5。

对于所有数据, 1 ≤ N , M ≤ 500 1 \le N,M \le 500 1≤N,M≤500。

思路

每个格子看作点。

如果当前电线是左上到右下:

左上到右下建边权为 0 的边。

右上到左下建边权为 1 的边表示需要改变。

反之亦然。

01 最短路,用队列维护。

AC代码
cpp 复制代码
#include<iostream>
#include<deque> 
#include<cstring>
using namespace std;
const int dx[4]={1,-1,-1,1},dy[4]={1,1,-1,-1},ix[4]={0,-1,-1,0},iy[4]={0,0,-1,-1};
const char a[5]="\\/\\/";
deque <int> x,y;
char map[505][505]; 
int vis [505][505];
int l,c;  
void bfs(){ 
	memset(vis,0x3f,sizeof(vis)); 
	x.push_back(0); 
	y.push_back(0);
	vis[0][0]=0; 
	while(!x.empty()){ 
		int xx=x.front(); 
		int yy=y.front();
		x.pop_front(); 
		y.pop_front();
		for(int i=0;i<4;i++){  
			int dnx=xx+dx[i]; 
			int dny=yy+dy[i];
			int inx=xx+ix[i];
			int iny=yy+iy[i];
			if(dnx>=0&&dnx<=l&&dny>=0&&dny<=c){  
				if(a[i]!=map[inx][iny]){ 
					int t=vis[xx][yy]+1;  
					if(t<vis[dnx][dny]){
						x.push_back(dnx); 
						y.push_back(dny);
						vis[dnx][dny]=t; 	
					}
				}	
				else{
					int t=vis[xx][yy]; 
					if(t<vis[dnx][dny]){ 
						x.push_front(dnx);
						y.push_front(dny);
						vis[dnx][dny]=t;	
					}
				}
			}
		}
	}
	cout<<vis[l][c]<<endl; 
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>l>>c; 
	for(int i=0;i<l;i++)
		cin>>map[i];
	if((l+c)%2) 
		cout<<"NO SOLUTION\n";
	else bfs(); 
	return 0;		
} 

P1462 通往奥格瑞玛的道路

题目背景

在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量。

有一天他醒来后发现自己居然到了联盟的主城暴风城。

在被众多联盟的士兵攻击后,他决定逃回自己的家乡奥格瑞玛。

题目描述

在艾泽拉斯,有 n n n 个城市。编号为 1 , 2 , 3 , ... , n 1,2,3,\ldots,n 1,2,3,...,n。

城市之间有 m m m 条双向的公路,连接着两个城市,从某个城市到另一个城市,会遭到联盟的攻击,进而损失一定的血量。

每次经过一个城市,都会被收取一定的过路费(包括起点和终点)。路上并没有收费站。

假设 1 1 1 为暴风城, n n n 为奥格瑞玛,而他的血量最多为 b b b,出发时他的血量是满的。如果他的血量降低至负数,则他就无法到达奥格瑞玛。

歪嘴哦不希望花很多钱,他想知道,在所有可以到达奥格瑞玛的道路中,对于每条道路所经过的城市收费的最大值,最小值为多少。

输入格式

第一行 3 3 3 个正整数, n , m , b n,m,b n,m,b。分别表示有 n n n 个城市, m m m 条公路,歪嘴哦的血量为 b b b。

接下来有 n n n 行,每行 1 1 1 个正整数, f i f_i fi。表示经过城市 i i i,需要交费 f i f_i fi 元。

再接下来有 m m m 行,每行 3 3 3 个正整数, a i , b i , c i a_i,b_i,c_i ai,bi,ci( 1 ≤ a i , b i ≤ n 1\leq a_i,b_i\leq n 1≤ai,bi≤n)。表示城市 a i a_i ai 和城市 b i b_i bi 之间有一条公路,如果从城市 a i a_i ai 到城市 b i b_i bi,或者从城市 b i b_i bi 到城市 a i a_i ai,会损失 c i c_i ci 的血量。

输出格式

仅一个整数,表示歪嘴哦交费最多的一次的最小值。

如果他无法到达奥格瑞玛,输出 AFK

样例 #1
样例输入 #1
4 4 8
8
5
6
10
2 1 2
2 4 1
1 3 4
3 4 3
样例输出 #1
10
提示

对于 60 % 60\% 60% 的数据,满足 n ≤ 200 n\leq 200 n≤200, m ≤ 1 0 4 m\leq 10^4 m≤104, b ≤ 200 b\leq 200 b≤200;

对于 100 % 100\% 100% 的数据,满足 1 ≤ n ≤ 1 0 4 1\leq n\leq 10^4 1≤n≤104, 1 ≤ m ≤ 5 × 1 0 4 1\leq m\leq 5\times 10^4 1≤m≤5×104, 1 ≤ b ≤ 1 0 9 1\leq b\leq 10^9 1≤b≤109;

对于 100 % 100\% 100% 的数据,满足 1 ≤ c i ≤ 1 0 9 1\leq c_i\leq 10^9 1≤ci≤109, 0 ≤ f i ≤ 1 0 9 0\leq f_i\leq 10^9 0≤fi≤109,可能有两条边连接着相同的城市。

思路

最大值最小?二分!

保留二分值以下的边,跑最短路,检查是否能活着到达 n 号点

满分代码
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=10005,M=100005,MAXN=1000000005;
int f[N],dis[N],h[N];
bool ed[N];
int n,m,b,t;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >qu;
struct edge{
	int to,ne,w;
}e[M];
void add(int x,int y,int z){
	e[++t].w=z;
	e[t].to=y;
	e[t].ne=h[x];
	h[x]=t;
}
bool work(int x){
	if(x<f[1])return 0;
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(ed,0,sizeof(ed));
	dis[1]=0;
	qu.push({0,1});
	int s1,s2;
	while(qu.size()){
		s1=qu.top().second;qu.pop();
		if(ed[s1])continue;
		ed[s1]=1;
		s2=h[s1];
		while(s2){
			if(f[e[s2].to]<=x&&ed[e[s2].to]==0&&dis[s1]+e[s2].w<dis[e[s2].to]){
				dis[e[s2].to]=dis[s1]+e[s2].w;
				qu.push({dis[e[s2].to],e[s2].to});
			}
			s2=e[s2].ne;
		}
	}
	if(dis[n]<b)return 1;
	return 0;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>b;
	for(int i=1;i<=n;i++)
		cin>>f[i];
	int a1,a2,a3;
	for(int i=1;i<=m;i++){
		cin>>a1>>a2>>a3;
		add(a1,a2,a3);
		add(a2,a1,a3);
	}
	if(work(MAXN)==0){
		cout<<"AFK"<<endl;
		return 0;
	}
	int l=1,r=MAXN,mid=(l+r)>>1;
	int c;
	while(l<=r){
		c=work(mid);
		if(c)
			r=mid-1;
		else
			l=mid+1;
		mid=(l+r)>>1;
	}
	cout<<l<<endl;
	return 0;
}

P1119 灾后重建(Floyd 理解)

题目背景

B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。

题目描述

给出 B 地区的村庄数 N N N,村庄编号从 0 0 0 到 N − 1 N-1 N−1,和所有 M M M 条公路的长度,公路是双向的。并给出第 i i i 个村庄重建完成的时间 t i t_i ti,你可以认为是同时开始重建并在第 t i t_i ti 天重建完成,并且在当天即可通车。若 t i t_i ti 为 0 0 0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 Q Q Q 个询问 ( x , y , t ) (x,y,t) (x,y,t),对于每个询问你要回答在第 t t t 天,从村庄 x x x 到村庄 y y y 的最短路径长度为多少。如果无法找到从 x x x 村庄到 y y y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x x x 或村庄 y y y 在第 t t t 天仍未重建完成,则需要输出 − 1 -1 −1。

输入格式

第一行包含两个正整数 N , M N,M N,M,表示了村庄的数目与公路的数量。

第二行包含 N N N 个非负整数 t 0 , t 1 , ⋯   , t N − 1 t_0,t_1,\cdots,t_{N-1} t0,t1,⋯,tN−1,表示了每个村庄重建完成的时间,数据保证了 t 0 ≤ t 1 ≤ ⋯ ≤ t N − 1 t_0 \le t_1 \le \cdots \le t_{N-1} t0≤t1≤⋯≤tN−1。

接下来 M M M 行,每行 3 3 3 个非负整数 i , j , w i,j,w i,j,w, w w w 不超过 10000 10000 10000,表示了有一条连接村庄 i i i 与村庄 j j j 的道路,长度为 w w w,保证 i ≠ j i\neq j i=j,且对于任意一对村庄只会存在一条道路。

接下来一行也就是 M + 3 M+3 M+3 行包含一个正整数 Q Q Q,表示 Q Q Q 个询问。

接下来 Q Q Q 行,每行 3 3 3 个非负整数 x , y , t x,y,t x,y,t,询问在第 t t t 天,从村庄 x x x 到村庄 y y y 的最短路径长度为多少,数据保证了 t t t 是不下降的。

输出格式

共 Q Q Q 行,对每一个询问 ( x , y , t ) (x,y,t) (x,y,t) 输出对应的答案,即在第 t t t 天,从村庄 x x x 到村庄 y y y 的最短路径长度为多少。如果在第 t t t 天无法找到从 x x x 村庄到 y y y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x x x 或村庄 y y y 在第 t t t 天仍未修复完成,则输出 − 1 -1 −1。

样例 #1
样例输入 #1
4 5
1 2 3 4
0 2 1
2 3 1
3 1 2
2 1 4
0 3 5
4
2 0 2
0 1 2
0 1 3
0 1 4
样例输出 #1
-1
-1
5
4
提示
  • 对于 30 % 30\% 30% 的数据,有 N ≤ 50 N\le 50 N≤50;
  • 对于 30 % 30\% 30% 的数据,有 t i = 0 t_i=0 ti=0,其中有 20 % 20\% 20% 的数据有 t i = 0 t_i=0 ti=0 且 N > 50 N>50 N>50;
  • 对于 50 % 50\% 50% 的数据,有 Q ≤ 100 Q\le 100 Q≤100;
  • 对于 100 % 100\% 100% 的数据,有 1 ≤ N ≤ 200 1\le N\le 200 1≤N≤200, 0 ≤ M ≤ N × ( N − 1 ) 2 0\le M\le \dfrac{N\times(N-1)}{2} 0≤M≤2N×(N−1), 1 ≤ Q ≤ 50000 1\le Q\le 50000 1≤Q≤50000,所有输入数据涉及整数均不超过 1 0 5 10^5 105。
思路

考虑 Floyd 的算法意义 -- 只经过前 k 个点的最短路径,将村庄按重建时间排序,离线下

来在对应的位置直接询问即可。

AC代码
cpp 复制代码
#include<bits/stdc++.h>
#define N 205
using namespace std;
int n,m;
int a[N];
int f[N][N];
inline void updata(int k){
	for(int i=0;i<n;i++)
	for(int j=0;j<n;j++)
	if(f[i][j]>f[i][k]+f[j][k])
	f[i][j]=f[j][i]=f[i][k]+f[j][k];
	return;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=0;i<n;i++)
		cin>>a[i];
	memset(f,0x3f3f3f3f,sizeof(f));
	for(int i=0;i<n;i++)f[i][i]=0;
	int s1,s2,s3;
	for(int i=1;i<=m;i++){
		cin>>s1>>s2>>s3;
		f[s1][s2]=f[s2][s1]=s3; 
	}
	int q;
	cin>>q;
	int now=0;
	for(int i=1;i<=q;i++){
		cin>>s1>>s2>>s3;
		while(a[now]<=s3&&now<n){
			updata(now);
			now++;
		}
		if(a[s1]>s3||a[s2]>s3)cout<<-1<<endl;
		else {
			if(f[s1][s2]==0x3f3f3f3f)cout<<-1<<endl;
			else cout<<f[s1][s2]<<endl;
		}
	}
	return 0;
} 

P4822 [BJWC2012] 冻结(分层图)

题目背景

"我要成为魔法少女!"

"那么,以灵魂为代价,你希望得到什么?"

"我要将有关魔法和奇迹的一切,封印于卡片之中„„"

在这个愿望被实现以后的世界里,人们享受着魔法卡片(SpellCard,又名符卡)带来的便捷。

现在,不需要立下契约也可以使用魔法了!你还不来试一试?

比如,我们在魔法百科全书(Encyclopedia of Spells)里用"freeze"作为关键字来查询,会有很多有趣的结果。

例如,我们熟知的 Cirno,她的冰冻魔法当然会有对应的 SpellCard 了。当然,更加令人惊讶的是,居然有冻结时间的魔法,Cirno 的冻青蛙比起这些来真是小巫见大巫了。

这说明之前的世界中有很多魔法少女曾许下控制时间的愿望,比如 Akemi Homura、Sakuya Izayoi、......

当然,在本题中我们并不是要来研究历史的,而是研究魔法的应用。

题目描述

我们考虑最简单的旅行问题吧: 现在这个大陆上有 N N N 个城市, M M M 条双向的道路。城市编号为 1 1 1 ~ N N N,我们在 1 1 1 号城市,需要到 N N N 号城市,怎样才能最快地到达呢?

这不就是最短路问题吗?我们都知道可以用 Dijkstra、Bellman-Ford、Floyd-Warshall等算法来解决。

现在,我们一共有 K K K 张可以使时间变慢 50%的 SpellCard,也就是说,在通过某条路径时,我们可以选择使用一张卡片,这样,我们通过这一条道路的时间 就可以减少到原先的一半。需要注意的是:

  1. 在一条道路上最多只能使用一张 SpellCard。
  2. 使用一张SpellCard 只在一条道路上起作用。
  3. 你不必使用完所有的 SpellCard。

给定以上的信息,你的任务是:求出在可以使用这不超过 K K K 张时间减速的 SpellCard 之情形下,从城市 1 1 1 到城市 N N N 最少需要多长时间。

输入格式

第一行包含三个整数: N N N、 M M M、 K K K。

接下来 M M M 行,每行包含三个整数: A i A_i Ai、 B i B_i Bi、 T i m e i Time_i Timei,表示存在一条 A i A_i Ai 与 B i B_i Bi 之间的双向道路,在不使用 SpellCard 之前提下,通过它需要 T i m e i Time_i Timei 的时间。

输出格式

输出一个整数,表示从 1 1 1 号城市到 N N N 号城市的最小用时。

样例 #1
样例输入 #1
4 4 1 
1 2 4 
4 2 6 
1 3 8 
3 4 8
样例输出 #1
7
提示
样例 1 解释

在不使用 SpellCard 时,最短路为 1 → 2 → 4 1 \to 2 \to 4 1→2→4,总时间为 10。现在我们可以使用 1 次 SpellCard,那么我们将通过 2 → 4 2 \to 4 2→4 这条道路的时间减半,此时总时间为7。

数据规模与约定

对于 100 % 100\% 100% 的数据,保证:

  • 1 ≤ K ≤ N ≤ 50 1 \leq K \leq N \leq 50 1≤K≤N≤50, M ≤ 1 0 3 M \leq 10^3 M≤103。
  • 1 ≤ A i , B i ≤ N 1 \leq A_i,B_i \leq N 1≤Ai,Bi≤N, 2 ≤ T i m e i ≤ 2 × 1 0 3 2 \leq Time_i \leq 2 \times 10^3 2≤Timei≤2×103。
  • 为保证答案为整数,保证所有的 T i m e i Time_i Timei 均为偶数。
  • 所有数据中的无向图保证无自环、重边,且是连通的。
思路

分层图,建 k 层点跑最短路。

另一视角:动态规划

AC代码
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=10000000,INF=0x3f3f3f3f; 
struct node{
	int to,ne,w;
}e[N*2];
int n,m,k,x,y,z,idx,dis[N],vis[N],h[N];
int point(int x,int y){return y*n+x;}
int min(int a,int b){return a<b?a:b;}
void add(int u,int v,int w){e[++idx].ne=h[u],e[idx].to=v,e[idx].w=w,h[u]=idx;}
int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
queue<int> q;
void spfa(){
	for(int i=1;i<=point(n,k);i++) dis[i]=INF;
	dis[1]=0;vis[1]=1;
	q.push(1);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=h[u];i>0;i=e[i].ne){
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v]){
					q.push(v);
					vis[v]=1;
				}
			}
		}
		vis[u]=0;
	}
}
int main(){
	n=read();m=read();k=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),z=read();
		for(int j=0;j<=k;j++) add(point(x,j),point(y,j),z),add(point(y,j),point(x,j),z);
		for(int j=0;j<k;j++) add(point(x,j),point(y,j+1),z/2),add(point(y,j),point(x,j+1),z/2);
	}
	spfa();
	int ans=INF;
	for(int i=0;i<=k;i++) ans=min(ans,dis[point(n,i)]);
	printf("%d",ans);
	return 0;
}

[USACO07DEC] Sightseeing Cows G

题面翻译

给你一张 n n n 点 m m m 边的有向图,第 i i i 个点点权为 F i F_i Fi,第 i i i 条边边权为 T i T_i Ti。

找一个环,设环上的点组成的集合为 S S S,环的边组成的集合为 E E E,最大化 ∑ u ∈ S F u ∑ e ∈ E T e \dfrac{\sum_{u\in S}F_u}{\sum_{e\in E}T_e} ∑e∈ETe∑u∈SFu。

数据范围: 1 ≤ n , F i , T i ≤ 1 0 3 1\leq n,F_i,T_i\leq 10^3 1≤n,Fi,Ti≤103, 1 ≤ m ≤ 5 × 1 0 3 1\leq m\leq 5\times10^3 1≤m≤5×103。

题目描述

Farmer John has decided to reward his cows for their hard work by taking them on a tour of the big city! The cows must decide how best to spend their free time.

Fortunately, they have a detailed city map showing the L (2 ≤ L ≤ 1000) major landmarks (conveniently numbered 1... L) and the P (2 ≤ P ≤ 5000) unidirectional cow paths that join them. Farmer John will drive the cows to a starting landmark of their choice, from which they will walk along the cow paths to a series of other landmarks, ending back at their starting landmark where Farmer John will pick them up and take them back to the farm. Because space in the city is at a premium, the cow paths are very narrow and so travel along each cow path is only allowed in one fixed direction.

While the cows may spend as much time as they like in the city, they do tend to get bored easily. Visiting each new landmark is fun, but walking between them takes time. The cows know the exact fun values Fi (1 ≤ Fi ≤ 1000) for each landmark i.

The cows also know about the cowpaths. Cowpath i connects landmark L1i to L2i (in the direction L1i -> L2i ) and requires time Ti (1 ≤ Ti ≤ 1000) to traverse.

In order to have the best possible day off, the cows want to maximize the average fun value per unit time of their trip. Of course, the landmarks are only fun the first time they are visited; the cows may pass through the landmark more than once, but they do not perceive its fun value again. Furthermore, Farmer John is making the cows visit at least two landmarks, so that they get some exercise during their day off.

Help the cows find the maximum fun value per unit time that they can achieve.

输入格式

* Line 1: Two space-separated integers: L and P

* Lines 2...L+1: Line i+1 contains a single one integer: Fi

* Lines L+2...L+P+1: Line L+i+1 describes cow path i with three space-separated integers: L1i , L2i , and Ti

输出格式

* Line 1: A single number given to two decimal places (do not perform explicit rounding), the maximum possible average fun per unit time, or 0 if the cows cannot plan any trip at all in accordance with the above rules.

样例 #1
样例输入 #1
5 7
30
10
10
5
10
1 2 3
2 3 2
3 4 5
3 5 2
4 5 5
5 1 3
5 2 2
样例输出 #1
6.00
思路

二分答案, ∑ F / ∑ T ≥ k , ∑ F − k T ≥ 0 \sum F/\sum T \geq k,\sum F- kT \geq 0 ∑F/∑T≥k,∑F−kT≥0。

那么问题即为判断是否存在一个正环,spfa 跑最长路即可(取反判负环也可)。

AC代码
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+7,M = 5e4+7;
const double eps = 1e-6;
int f[N],h[N],ver[M],ne[M],idx,n,m,a[N],cnt[N];
double e[M],d[N]; 
bool vis[N];
void add(int x,int y,double z){
    ver[++idx] = y;
    ne[idx] = h[x];
    e[idx] = z;
    h[x] = idx;
}
bool spfa(){
    queue<int>q;
    for(int i = 1;i <= n;i++){
        q.push(i);
        d[i] = 0;
        vis[i] = 1;
    }
    memset(cnt,0,sizeof cnt);
    while(q.size()){
        int x = q.front();
        q.pop();
        vis[x] = 0;
        for(int i = h[x];i;i = ne[i]){
            int y = ver[i];
            if(d[y] > d[x] + e[i]){
                d[y] = d[x] + e[i];
                if((cnt[y] = cnt[x] + 1 ) > n)return 1;
                if(!vis[y]){
                    q.push(y);
                    vis[y] = 1;
                }
            }
        }
        
    }
    return 0;
}
int x[N],y[N],z[N];
bool check(double w){
    idx = 0;
    memset(h,0,sizeof h);
    for(int i = 1;i <= m;i++)
        add(x[i],y[i],w * z[i] - f[x[i]]);
    return spfa();
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++)scanf("%d",&f[i]);
    for(int i = 1;i <= m;i++)scanf("%d %d %d",&x[i],&y[i],&z[i]);
    double l = 0,r = 1000;
    while(r - l > eps){
        double mid = (l + r) / 2;
        if(check(mid))l = mid;
        else r = mid;
    }
    printf("%.2f",l);
    return 0;
}

[AHOI2014/JSOI2014] 骑士游戏

题目背景

长期的宅男生活中,JYY 又挖掘出了一款 RPG 游戏。在这个游戏中 JYY 会扮演一个英勇的骑士,用他手中的长剑去杀死入侵村庄的怪兽。

题目描述

在这个游戏中,JYY 一共有两种攻击方式,一种是普通攻击,一种是法术攻击。两种攻击方式都会消耗 JYY 一些体力。采用普通攻击进攻怪兽并不能把怪兽彻底杀死,怪兽的尸体可以变出其他一些新的怪兽,注意一个怪兽可能经过若干次普通攻击后变回一个或更多同样的怪兽;而采用法术攻击则可以彻底将一个怪兽杀死。当然了,一般来说,相比普通攻击,法术攻击会消耗更多的体力值(但由于游戏系统 bug,并不保证这一点)。

游戏世界中一共有 N N N 种不同的怪兽,分别由 1 1 1 到 N N N 编号,现在 1 1 1 号怪兽入侵村庄了,JYY 想知道,最少花费多少体力值才能将所有村庄中的怪兽全部杀死呢?

输入格式

第一行包含一个整数 N N N。

接下来 N N N 行,每行描述一个怪兽的信息;

其中第 i i i 行包含若干个整数,前三个整数为 S i S_i Si, K i K_i Ki 和 R i R_i Ri,表示对于 i i i 号怪兽,普通攻击需要消耗 S i S_i Si 的体力,法术攻击需要消耗 K i K_i Ki 的体力,同时 i i i 号怪兽死亡后会产生 R i R_i Ri 个新的怪兽。表示一个新出现的怪兽编号。同一编号的怪兽可以出现多个。

输出格式

输出一行一个整数,表示最少需要的体力值。

样例 #1
样例输入 #1
4
4 27 3 2 3 2
3 5 1 2
1 13 2 4 2
5 6 1 2
样例输出 #1
26
提示

首先用消耗 4 4 4 点体力用普通攻击,然后出现的怪兽编号是 2 2 2, 2 2 2 和 3 3 3。花费 10 10 10 点体力用法术攻击杀死两个编号为 2 2 2 的怪兽。剩下 3 3 3 号怪兽花费 1 1 1 点体力进行普通攻击。此时村庄里的怪兽编号是 2 2 2 和 4 4 4。最后花费 11 11 11 点体力用法术攻击将这两只怪兽彻底杀死。一共花费的体力是 4 + 5 + 5 + 1 + 5 + 6 = 26 4+5+5+1+5+6=26 4+5+5+1+5+6=26。

对于所有数据 2 ≤ N ≤ 2 × 1 0 5 2 \le N \le 2 \times 10^5 2≤N≤2×105, 1 ≤ R i , ∑ R i ≤ 1 0 6 1 \le R_i,\sum R_i \le 10^6 1≤Ri,∑Ri≤106, 1 ≤ K i , S i ≤ 5 × 1 0 14 1 \le K_i,S_i \le 5 \times 10^{14} 1≤Ki,Si≤5×1014。

思路

设 f [ x ] f[x] f[x]表示杀死 x 的最小代价,那么有 f [ x ] = m i n ( S x + ∑ f [ y ] , K i ) f[x]=min(S_x+\sum f[y],K_i) f[x]=min(Sx+∑f[y],Ki)

显然这个转移会有环,并且是一个取 min 的转移,那么类比最短路,类比 dijistra,刚开

始令 f [ x ] = K i f[x]=K_i f[x]=Ki,并扔到堆里,每次选出最小的并更新其他人的 dp 值。

理解:大的 dp 值一定由小的 dp 来转移,那么从最小的 dp 值开始,则无后效性。

AC代码
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read() {
	char cc = getchar(); ll cn = 0, flus = 1;
	while(cc < '0' || cc > '9') {  if( cc == '-' ) flus = -flus;  cc = getchar();  }
	while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
	return cn * flus;
}
const ll N = 2e5 + 5 ,M = 1e6 + 5 ; 
ll n, s[N], k[N], dp[N], vis[N], ans[N], r[N] ;
vector<int> mp[N] ; 
struct node {
	ll id, w ; 
	bool operator < (const node& x) const {
		return w > x.w ; 
	}
};
priority_queue<node> q ; 
int main()
{
	n = read() ; 
	ll x, siz ; 
	for(ll i=1;i<=n;i++)	{
		s[i] = read(), k[i] = read(), r[i] = read() ;
		for(ll j= 1; j<=r[i];j++) x = read(), mp[x].push_back(i) ; 
		q.push((node){i,k[i]}), dp[i]=s[i] ;
	}
	while( !q.empty() ) {
		ll u = q.top().id, w = q.top().w ; q.pop() ; 
		if( vis[u] ) continue ; 
		vis[u] = 1, ans[u] = w, siz = mp[u].size() - 1 ; 
		for(ll i=0;i<=siz;i++) {
			ll v = mp[u][i] ; 
			if(vis[v]||dp[v]>k[v]) continue ; 
			r[v]--;
			dp[v]+=w ;
			if(r[v]==0)q.push((node){v,dp[v]}) ;
		}
	}
	printf("%lld\n",ans[1]) ;
	return 0;
}

P9402 [POI2020-2021R3] Droga do domu(分层图)

题目背景

译自 XXVIII Olimpiada Informatyczna - III etap Droga do domu

d1t1。

题目描述

n n n 个点, m m m 条边,无重边自环,边有长度。

1 1 1 号点是学校, n n n 号点是家。

s s s 条公交线路。公交逢点必停,且一个点不会停两次。在一条边上行驶的时间就是它的长度。给定了第一班公交发车时间和发车间隔。

在时刻 t t t 从学校出发,至多换乘 k k k 次,求最早什么时候到家。

只计算路上时间和等车时间。换乘时间不计。

输入格式

第一行:五个整数 n , m , s , k , t n,m,s,k,t n,m,s,k,t。

接下来 m m m 行:每行三个整数 a , b , c a,b,c a,b,c,表示有一条边连接 a , b a,b a,b,长度为 c c c。

接下来 2 s 2s 2s 行:每两行描述一条公交线路:

  • 第一行三个整数 l , x , y l,x,y l,x,y,表示它共停靠 l l l 个点,第一班在时刻 x x x 发车,每两班之间时间间隔为 y y y。
  • 第二行 l l l 个整数 v 1 , ... , v l v_1,\dots,v_l v1,...,vl,依次为它停靠的 l l l 个点。
输出格式

一行一个整数,答案。

如果不能到家,那么输出一行一个字符串 NIE

样例 #1
样例输入 #1
4 4 2 1 1
1 2 2
2 3 4
1 3 3
4 3 2
4 0 10
1 2 3 4
3 2 7
1 3 2
样例输出 #1
8
样例 #2
样例输入 #2
10 45 17 10 123
1 2 1
1 3 100
1 4 100
1 5 100
1 6 100
1 7 100
1 8 100
1 9 100
1 10 100
2 3 1
2 4 100
2 5 100
2 6 100
2 7 100
2 8 100
2 9 100
2 10 100
3 4 1
3 5 100
3 6 100
3 7 100
3 8 100
3 9 100
3 10 100
4 5 1
4 6 100
4 7 100
4 8 100
4 9 100
4 10 100
5 6 1
5 7 100
5 8 100
5 9 100
5 10 100
6 7 1
6 8 100
6 9 100
6 10 100
7 8 1
7 9 100
7 10 100
8 9 1
8 10 100
9 10 1
2 0 1
1 2
2 0 1
1 3
2 0 1
2 3
2 0 1
2 4
2 0 1
3 4
2 0 1
3 5
2 0 1
4 5
2 0 1
4 6
2 0 1
5 6
2 0 1
5 7
2 0 1
6 7
2 0 1
6 8
2 0 1
7 8
2 0 1
7 9
2 0 1
8 9
2 0 1
8 10
2 0 1
9 10
样例输出 #2
132

提示

样例解释:

对于全部数据, 2 ≤ n ≤ 10000 2\leq n\leq 10000 2≤n≤10000, 1 ≤ m ≤ 50000 1\leq m\leq 50000 1≤m≤50000, 1 ≤ s ≤ 25000 1\leq s\leq 25000 1≤s≤25000, 0 ≤ k ≤ 100 0\leq k\leq 100 0≤k≤100, 0 ≤ t ≤ 1 0 9 0\leq t\leq 10^9 0≤t≤109, 1 ≤ c ≤ 1 0 9 1\leq c\leq 10^9 1≤c≤109, 2 ≤ l ≤ n 2\leq l\leq n 2≤l≤n, 0 ≤ x ≤ 1 0 9 0\leq x\leq 10^9 0≤x≤109, 1 ≤ y ≤ 1 0 9 1\leq y\leq 10^9 1≤y≤109, 1 ≤ a , b , v ≤ n 1\leq a,b,v\leq n 1≤a,b,v≤n, ∑ l ≤ 50000 \sum l\leq 50000 ∑l≤50000。

子任务编号 限制 分数
1 k = n k=n k=n 20
2 v i < v i + 1 v_i<v_{i+1} vi<vi+1 20
3 l = 2 l=2 l=2 20
4 t = 0 , x = 0 , y = 1 t=0,x=0,y=1 t=0,x=0,y=1 20
5 20
思路

按照k分层,设f[k][i]表示换成了 k 次,到达第i个点最早什么时间到。

如何转移?因为公交车线路经过哪些点是有顺序的,我们枚举每条线路,从前向后走即

可。

AC代码
cpp 复制代码
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
const int N=10005,M=50005,K=105;
#define ll long long
using namespace std;
int n,m,s,k,cnt,rt[M];
ll t,INF,ans,f[M][K];
struct edg {
	int to;
	ll val;
};
struct node 
{
	int to;
	ll len;
	ll st,ti;
}a[M];
struct que{
	int x,k;
	ll t;
	bool bj;
};
vector<edg> g[N];
vector<int> p[N];
queue<que> q;
ll read()
{
    ll res=0;char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch-'0'),ch=getchar();
    return res;
}
void add(int x,int y,ll z,ll st,ll ti) {
	a[x].to=y;
	a[x].len=z;
	a[x].st=st;
	a[x].ti=ti;
}
bool cmp(edg x,edg y) {
	return x.to<y.to;
}
ll calc(ll nowt,ll st,ll ti)
{
    if (nowt<=st) return st;
    ll tn=(nowt-st)/ti;
    if ((nowt-st)%ti!=0) tn++;
    return tn*ti+st;
}
int main()
{
    n=read();m=read();s=read();k=read();t=read();
	for (int i=1;i<=m;++i)
	{
		int x,y;ll z;
        x=read();y=read();z=read();
		g[x].push_back((edg){y,z});
		g[y].push_back((edg){x,z});
	}
    for (int i=1;i<=n;++i)
        sort(g[i].begin(),g[i].end(),cmp);
	for (int i=1;i<=s;++i)
	{
		int num=read();ll x=read(),y=read();
		int lst=0,u=0;
		for (int j=1;j<=num;++j)
		{
			int v;v=read();
			++cnt;
			p[v].push_back(cnt);rt[cnt]=v;
			if (!u) u=v,lst=cnt;
			else
			{
				int l=0,r=g[u].size()-1,mid,res=0;
				while (l<=r)
				{
					mid=(l+r)>>1;
					if (v<=g[u][mid].to) res=mid,r=mid-1;
					else l=mid+1;
				}
				add(lst,cnt,g[u][res].val,x,y);
				lst=cnt;x+=g[u][res].val;u=v;
			}
		}
	}
    memset(f,127,sizeof(f));
    INF=f[0][0];
    for (int i=0;i<p[1].size();++i)
        f[p[1][i]][0]=t;
    for (int x=1;x<=cnt;++x)
    {
        int y=a[x].to;
        if (!y) continue;
        ll nxt=calc(f[x][0],a[x].st,a[x].ti)+a[x].len;
        f[y][0]=min(f[y][0],nxt);
    }
    for (int j=1;j<=k;++j)
    {
        for (int now=1;now<=n;++now)
        {
            ll mn=INF;
            for (int i=0;i<p[now].size();++i)
                mn=min(mn,f[p[now][i]][j-1]);
            for (int i=0;i<p[now].size();++i)
                f[p[now][i]][j]=mn;
        }
        for (int x=1;x<=cnt;++x)
        {
            int y=a[x].to;
            if (!y) continue;
            ll nxt=calc(f[x][j],a[x].st,a[x].ti)+a[x].len;
            f[y][j]=min(f[y][j],nxt);
        }
    }
    ans=INF;
    for (int i=0;i<p[n].size();++i)
        for (int j=0;j<=k;++j)
            ans=min(ans,f[p[n][i]][j]);
    if (ans>=INF) printf("NIE\n");
    else printf("%lld\n",ans);
	return 0;
}

这是我的第二十七篇文章,如有纰漏也请各位大佬指正

辛苦创作不易,还望看官点赞收藏打赏,后续还会更新新的内容。

相关推荐
人才程序员6 分钟前
详解Qt 中使用虚拟键盘(软键盘qtvirtualkeyboard)
开发语言·数据库·c++·qt·计算机外设·界面·c语音
喵呜角角8 分钟前
QML TableView 实例演示 + 可能遇到的一些问题(Qt_6_5_3)
开发语言·windows·qt·qml·qt quick
撸码到无法自拔8 分钟前
R语言贝叶斯分析:INLA 、MCMC混合模型、生存分析肿瘤临床试验、间歇泉喷发时间数据应用|附数据代码...
开发语言·r语言
SSONICX8 分钟前
QT:生成二维码 QRCode
开发语言·qt
老汉忒cpp12 分钟前
Qt-系统相关(1)事件&文件
开发语言·qt
小宋102131 分钟前
实现java执行kettle并传参数
java·开发语言·etl
FreeLikeTheWind.1 小时前
C语言实例之9斐波那契数列实现
c语言·开发语言·算法
C++忠实粉丝1 小时前
计算机网络之应用层协议HTTP
linux·网络·c++·网络协议·tcp/ip·计算机网络·http
爱编程— 的小李1 小时前
文件的处理(c语言)
c语言·开发语言
monkey_meng1 小时前
【Rust Iterator 之 fold,map,filter,for_each】
开发语言·后端·rust