A-Til the Cows Come Home
题目链接
思路:经典dijkstra问题
vis数组:标记,看是否得到过该点的最短路径
dis数组:记录下从起点到该点的最短路径,每次要更新
mp数组:存图,一定要注意初始化mp数组
步骤:
1、初始化各类辅助数据结构
2、外层1~n-1循环(找n-1次),内层找最小+更新当前最小
3、输出所要的结果
#include <iostream>
#include <stdio.h>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e3+10;
int t, n;
int a, b, c;
int mp[maxn][maxn];
int dis[maxn];
int vis[maxn];
void dijkstra(){
vis[1]=1;
for(int i=1; i<n; i++){
int ans=MAX, k=1;
for(int j=1; j<=n; j++){
if(!vis[j]&&ans>dis[j]){
ans=dis[j];
k=j;
}
}
vis[k]=1;
for(int j=1; j<=n; j++){
if(dis[j]>dis[k]+mp[k][j]) dis[j]=dis[k]+mp[k][j];
}
}
cout << dis[n];
}
int main(){
cin >> t >> n;
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(i==j) continue;
else mp[i][j]=MAX;
}
}
for(int i=1; i<=t; i++){
scanf("%d%d%d",&a,&b,&c);
if(c<mp[a][b]){//这里要注意,可能一条路有多个输入,取最小的
mp[a][b]=c, mp[b][a]=c;
}
}
for(int i=1; i<=n; i++){
dis[i]=mp[1][i];
}
dijkstra();
return 0;
}
B-Frogger
题目链接
思路:该题意思是在所有通路中的最长边中寻找最短边即可
那么我们就把所有的道路长度存到一个w数组中,同时用a和b数组记下两点,最终用dijkstra更新n-1次,即可得到最小的必要距离。
#include <iostream>
#include <stdio.h>
#include <cmath>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 2e2+10;
int n;
struct node{
int x,y;
}mp[maxn];
double dis[maxn], w[40010];
int a[40010], b[40010];
int main(){
int k=0;
while(cin >> n && n!=0){
for(int i=1; i<=n; i++){
scanf("%d %d",&mp[i].x,&mp[i].y);
}
int h=0;
for(int i=1; i<n; i++){
for(int j=i+1; j<=n; j++){
w[++h]=sqrt((mp[i].x-mp[j].x)*(mp[i].x-mp[j].x)+(mp[i].y-mp[j].y)*(mp[i].y-mp[j].y));
a[h]=i;
b[h]=j;
}
}
for(int i=1; i<=n; i++) dis[i]=MAX;
dis[1]=0;
for(int i=1; i<=n-1; i++){
for(int j=1; j<=h; j++){
if(dis[a[j]]>max(dis[b[j]],w[j])) dis[a[j]]=max(dis[b[j]],w[j]);
if(dis[b[j]]>max(dis[a[j]],w[j])) dis[b[j]]=max(dis[a[j]],w[j]);
}
}
printf("Scenario #%d\nFrog Distance = %.3f\n\n",++k,dis[2]);
}
return 0;
}
C-Heavy Transportation
题目链接
思路:dijkstra的变形
问题的实质是叫我们找每条可到达n路径中所有最短边中的最大边,那么可以把dijkstra算法中的查询和更新部分进行改变,如下:
查询:
k=1;
for(int j=1; j<=n; j++){
if(!vis[j]&&dis[j]>dis[k]){
k=j;
}
}
//vis数组标记是否走过,dis数组储存当前点到源点所有路径中最短边的最长边。
//为什么选大的,原因是我要找最短边中的最大边,所以要把最大变得更大,才可以用它把别的值变得更大。
for(int j=1; j<=n; j++){
if(!vis[j]&&dis[j]<min(dis[k],mp[k][j])){
dis[j]=min(dis[k],mp[k][j]);
}
}
//最小边中的最大边,所以我们要取min找最小边,然后dis中的当前最优解比min中的最小边还小,那么我们就要刷新。
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <cstring>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e3+10;
int n, m, u, v, w, h, t;
int mp[maxn][maxn];
int dis[maxn];
int vis[maxn];
void dijkstra(){
vis[1]=1;
int ans, k;
for(int i=1; i<n; i++){
k=1;
for(int j=1; j<=n; j++){
if(!vis[j]&&dis[j]>dis[k]){
k=j;
}
}
vis[k]=1;
for(int j=1; j<=n; j++){
if(!vis[j]&&dis[j]<min(dis[k],mp[k][j])){
dis[j]=min(dis[k],mp[k][j]);
}
}
}
printf("Scenario #%d:\n%d\n\n",++h,dis[n]);
}
int main(){
cin >> t;
while(t--){
cin >> n >> m;
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
mp[i][j]=0;
}
}
memset(vis,0,sizeof(vis));
for(int i=1; i<=m; i++){
scanf("%d%d%d",&u,&v,&w);
mp[u][v]=mp[v][u]=w;
}
for(int i=1; i<=n; i++){
dis[i]=mp[1][i];
}
dijkstra();
for(int i=1; i<=n; i++){
cout << dis[i] << " ";
}
}
return 0;
}
D-Silver Cow Party
题目链接
思路:可以看到这题有多头牛往一个终点跑,然后从终点再回来,找来回用时最长的一头牛所花费的时间,且该图为单向图。
在普通的dijkstra中,我们的dis数组存的是每个点到源点的最短边权,但是在这里我们可以看到是多个牛到一个终点然后再回来,所以我们的dis数组可以用来存终点到每个牛的最短边权,然后将图转置,接着用dis数组存每个牛到终点的最短边权(其实就是反过来求最短边权),最后进行比较即可。
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <cmath>
#define MAX 0x3f3f3f3f
using namespace std;
const int maxn = 1e3+10;
typedef long long ll;
int n, m, x, a, b, t;
int vis[maxn], dis[maxn], disx[maxn];
int mp[maxn][maxn];
void dijkstra(){
memset(vis,0,sizeof(vis));
for(int i=1; i<=n; i++) dis[i]=mp[x][i];
vis[x]=1;
for(int i=1; i<n; i++){
int minx=MAX, k=1;
for(int j=1; j<=n; j++){
if(!vis[j]&&minx>dis[j]){
minx=dis[j], k=j;
}
}
vis[k]=1;
for(int j=1; j<=n; j++){
if(!vis[j]&&dis[j]>dis[k]+mp[k][j]) dis[j]=dis[k]+mp[k][j];
}
}
}
int main(){
cin >> n >> m >> x;
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(i==j) mp[i][j]=0;
else mp[i][j]=MAX;
}
}
for(int i=1; i<=m; i++){
scanf("%d%d%d",&a,&b,&t);
if(mp[a][b]>t) mp[a][b]=t;
}
dijkstra();
for(int i=1; i<=n; i++) disx[i]=dis[i];
for(int i=1; i<=n; i++){
for(int j=i+1; j<=n; j++){//注意一下一定是i+1,如果不是i+1,会转回去,会乱
int temp=mp[j][i];
mp[j][i]=mp[i][j];
mp[i][j]=temp;
}
}
dijkstra();
int ans=0;
for(int i=1; i<=n; i++){
if(ans<dis[i]+disx[i]) ans=dis[i]+disx[i];
}
printf("%d",ans);
return 0;
}
E-Wormholes
题目链接
思路:这种题模板题,是否能够回到起点,取决于是否有负环,spfa和bell_man_ford都可以做
Bell_man_Ford
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <cmath>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e4+10;//一定要开足够,否则会RE
struct node{
int x, y;
int val;
}v[maxn];
int n, m, w, f;
int s, e, t;
int dis[maxn];
int k;
bool Bell_man_Ford(){
for(int i=1; i<=n; i++) dis[i]=MAX;
dis[1]=0;
for(int i=1; i<n; i++){
for(int j=1; j<=k; j++){
if(dis[v[j].y]>dis[v[j].x]+v[j].val){
dis[v[j].y]=dis[v[j].x]+v[j].val;
}
}
}
for(int i=1; i<=k; i++){
if(dis[v[i].y]>dis[v[i].x]+v[i].val){
return false;
}
}
return true;
}
int main(){
cin >> f;
while(f--){
k=0;
scanf("%d%d%d",&n,&m,&w);
for(int i=1; i<=m; i++){
scanf("%d%d%d",&s,&e,&t);
v[++k].x=s, v[k].y=e, v[k].val=t;
v[++k].x=e, v[k].y=s, v[k].val=t;
}
for(int i=1; i<=w; i++){
scanf("%d%d%d",&s,&e,&t);
v[++k].x=s, v[k].y=e, v[k].val=-t;
}
if(Bell_man_Ford()) puts("NO");
else puts("YES");
}
return 0;
}
F-Cow Contest
题目链接
思路:可以看到,我们是要寻找每一个牛是否能准确得到排名,那类似一个多源最短路问题,数据不大,n<500, 那Floyd可以用上。
解题关键:只要一头牛有n-1条胜负关系,那就可以确定他的排名。
方法:mp二维数组,谁赢谁标1,然后Floyd走一遍,最后判断即可。
#include <iostream>
using namespace std;
const int maxn = 1e2+10;
int n, m;
int mp[maxn][maxn];
int a, b;
int main(){
cin >> n >> m;
for(int i=1; i<=m; i++){
cin >> a >> b;
mp[a][b]=1;
}
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++) if(mp[i][k]&&mp[k][j]) mp[i][j]=1;
int sum=0;
for(int i=1; i<=n; i++){
int ans=0;
for(int j=1; j<=n; j++){
if(mp[i][j]||mp[j][i]) ans++;
//注意这里是或,因为要确定n-1个1,而关系是双向的,因此只要一个成立即可
}
if(ans==n-1) sum++;
}
cout << sum;
return 0;
}
G-Arbitrage
题目链接
思路:套利的意思是原本自己投入的财富最后出现了增长,就如给的test1,1美元走一圈后变成1.05美元。
把所有信息建成图,那么意思就是这其中存在一个环,回去之后使本钱增加,然后因为本钱增加,导致后面一系列的更新使本钱再增加,松弛n-1次后,第n次如果还能增加,则表明其中存在环,用dis数组记录本金能换取该货币的值,dis【1】存本金。
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <map>
#define MAX 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 3e4+10;
int n, m;
string s;
string str1, str2;
double rate;
struct node{
int st, ed;
double rat;
}mp[maxn];
map <string,int> k;
double dis[maxn];
bool Bell_man_Ford(){
memset(dis,0,sizeof(dis));
dis[1]=1;
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
if(dis[mp[j].ed]<dis[mp[j].st]*mp[j].rat){
dis[mp[j].ed]=dis[mp[j].st]*mp[j].rat;
//当到第n次还能更新,说明其中存在环
if(i==n) return true;
}
}
}
return false;
}
int main(){
int Case=0;
while(cin >> n && n){
for(int i=1; i<=n; i++){
cin >> s;
k[s]=i;
}
cin >> m;
for(int i=1; i<=m; i++){
cin >> str1 >> rate >> str2;
mp[i].st=k[str1], mp[i].ed=k[str2], mp[i].rat=rate;
}
if(Bell_man_Ford()) printf("Case %d: Yes\n",++Case);
else printf("Case %d: No\n",++Case);
}
return 0;
}
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
if(dis[mp[j].ed]<dis[mp[j].st]*mp[j].rat){
dis[mp[j].ed]=dis[mp[j].st]*mp[j].rat;
if(i==n) return true;
}
}
}
个人觉得这里可以改成如下:
for(int j=1; j<=m; j++){
if(dis[mp[j].ed]<dis[mp[j].st]*mp[j].rat){
dis[mp[j].ed]=dis[mp[j].st]*mp[j].rat;
if(i==n) return true;
}
}
无法证明正确性,毕竟数据过于水,但改完能过,请大佬指点。
题目还有一种做法,用Floyd做
插入一段代码
for(int k=1; k<=n; k++){
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
if(mp[i][j]<mp[i][k]*mp[k][j]) mp[i][j]=mp[i][k]*mp[k][j];
}
}
}