CF1528D-It‘s a bird! No, it‘s a plane! No, it‘s AaParsa!

该博客讨论了一道算法竞赛题目,涉及城市环形排列中的大炮和超人的移动问题。每个城市至少有一个大炮,炮口会顺时针移动,超人需要借助大炮在城市间移动。题目要求找到从每个城市到其他城市的最短时间。作者提供了两种解题思路,一种是预处理每个城市飞一次到达任意城市的最短时间,然后用Dijkstra算法求解;另一种是官方做法,对每个城市建立伪边,并处理等待时间。博客还给出了两种思路的C++实现代码。
摘要由CSDN通过智能技术生成

D - It’s a bird! No, it’s a plane! No, it’s AaParsa!

题目描述

n n n 个城市(按顺时针排列成一个环,编号从 0   n − 1 0 \text{~} n-1 0 n1 )中安装了 m m m 个大炮,每个城市安装了至少一个大炮,第 i i i 个大炮炮口初始指向城市 b i b_i bi ,每过一秒炮口顺时针挪动指向下一个城市,任何东西(包括人)从第 i i i 个城市的炮口发射升空后需要 c i c_i ci 秒到达发射时炮口指向的城市。

现在超人飞不了了,需要通过大炮发射自己来往。对每一对 ( u , v ) (u,v) (u,v) ,求从城市 u u u 到达城市 v v v 最少需要多少秒。

超人可以在任何一个城市停留任意时间。

数据范围与提示

2 ≤ n ≤ 600 , n ≤ m ≤ n 2 , 1 ≤ c i ≤ 1 0 9 2\le n\le 600,n\le m\le n^2,1\le c_i\le 10^9 2n600nmn21ci109

思路

题目描述不清楚的可以用百度翻译再看一下原题面。

思路一:

由于炮口的指向只和经过的时间有关系,所以如果我们预处理从每个城市飞一次到达任意一个城市的最短时间(包括飞之前的等待),那么就可以做 D i j Dij Dij 了。

首先这个预处理怎么搞呢?对每个点顺时针做一个简单DP即可。设 m v [ i ] [ j ] mv[i][j] mv[i][j] 为从 i i i j j j 飞一次的最少时间,没有炮口的就原地等炮口挪过来,那么显然有
初始值: m v [ a i ] [ b i ] = c i 转移: m v [ i ] [ j ] = min ⁡ ( m v [ i ] [ j ] , m v [ i ] [ j − 1 ] + 1 ) m v [ i ] [ 0 ] = min ⁡ ( m v [ i ] [ 0 ] , m v [ i ] [ n − 1 ] + 1 ) \text{初始值:}mv[a_i][b_i]=c_i \\ \text{转移:}mv[i][j]=\min(mv[i][j],mv[i][j-1]+1) \\ mv[i][0]=\min(mv[i][0],mv[i][n-1]+1) 初始值:mv[ai][bi]=ci转移:mv[i][j]=min(mv[i][j],mv[i][j1]+1)mv[i][0]=min(mv[i][0],mv[i][n1]+1)
循环转移即可。

但是这个 m v mv mv 值是默认到达点 i i i 时间为第 0 0 0 秒的,怎么用在 D i j Dij Dij 上呢?其实转动模拟一下就可以发现,第 s s s 秒到达点 i i i 、从 i i i j j j 的最短时间等于第 0 0 0 秒到达点 i i i 、从 i i i ( j − s )   m o d   n (j-s)\bmod n (js)modn 的最短时间,然后用 m v mv mv 做边权跑一遍 O ( n 2 ) O(n^2) O(n2) D i j Dij Dij 即可。

由于每个点要求一遍单源点最短路,所以复杂度 O ( n 3 ) O(n^3) O(n3)

谨告:以后不要再用 Floyd \text{Floyd} Floyd 了, Floyd \text{Floyd} Floyd D i j Dij Dij 面前毫无优点(常数小些?卡卡就一样了),而且 Floyd \text{Floyd} Floyd 能做的题 D i j Dij Dij 一定都能做, D i j Dij Dij 能做的 Floyd \text{Floyd} Floyd 不一定能做。

拿这题来说,由于路径并没有可合并的性质,边权要根据前一个点的最短路变化,所以用 Floyd \text{Floyd} FloydWA

思路二:

官方做法。

大致思路是和思路一差不多的,但是在处理“等待任意时间”这个条件时不用预处理DP,而是对每个 i i i ,向 ( i + 1 )   m o d   n (i+1)\bmod n (i+1)modn 连一条长度为1的伪边。伪边不旋转,实边(即大炮所构成的边)要旋转。

这个解法多了一个特判,但是理论上边数 = m + n ≤ n 2 + n =m+n\le n^2+n =m+nn2+n ,时间应该更优。

代码

思路一:

#include<cstdio>//JZM YYDS!!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#define ll long long
#define MAXN 605
#define uns unsigned
#define MOD 998244353ll
#define INF 0x7f7f7f7f
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
int n,m;
ll dp[MAXN][MAXN],mv[MAXN][MAXN];
bool vis[MAXN];
signed main()
{
	n=read(),m=read();
	memset(dp,0x7f,sizeof(dp));
	memset(mv,0x7f,sizeof(mv));
	for(int i=1;i<=m;i++){
		int a=read(),b=read();
		mv[a][b]=read();
	}
	for(int i=0;i<n;i++){
		for(int j=1;j<n;j++)mv[i][j]=min(mv[i][j],mv[i][j-1]+1);
		mv[i][0]=min(mv[i][0],mv[i][n-1]+1);
		for(int j=1;j<n;j++)mv[i][j]=min(mv[i][j],mv[i][j-1]+1);
		mv[i][0]=min(mv[i][0],mv[i][n-1]+1);
	}
	for(int x=0;x<n;x++){
		dp[x][x]=0,dp[x][n]=INF;
		for(int i=0;i<n;i++)vis[i]=0;
		for(int K=0;K<n;K++){
			int u=n;
			for(int i=0;i<n;i++)
				if(!vis[i]&&dp[x][i]<dp[x][u])u=i;
			if(u==n)break;vis[u]=1;
			int ad=((-dp[x][u])%n+n)%n;
			for(int i=0;i<n;i++)if(!vis[i])
				dp[x][i]=min(dp[x][i],dp[x][u]+mv[u][(i+ad)%n]);
		}
	}
	for(int i=0;i<n;i++){
		dp[i][i]=0;
		for(int j=0;j<n;j++)printf("%lld ",dp[i][j]);
		putchar('\n');
	}
	return 0;
}

思路二(官方代码):

#include <bits/stdc++.h>
#pragma GCC optimize ("O2,unroll-loops")
//#pragma GCC optimize("no-stack-protector,fast-math")
 
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<pii, int> piii;
typedef pair<ll, ll> pll;
#define debug(x) cerr<<#x<<'='<<(x)<<endl;
#define debugp(x) cerr<<#x<<"= {"<<(x.first)<<", "<<(x.second)<<"}"<<endl;
#define debug2(x, y) cerr<<"{"<<#x<<", "<<#y<<"} = {"<<(x)<<", "<<(y)<<"}"<<endl;
#define debugv(v) {cerr<<#v<<" : ";for (auto x:v) cerr<<x<<' ';cerr<<endl;}
#define all(x) x.begin(), x.end()
#define pb push_back
#define kill(x) return cout<<x<<'\n', 0;
 
const int inf=1000000010;
const ll INF=1000000000000001000LL;
const int mod=1000000007;
const int N=605;
 
int n, m, k, u, v, x, y, t, a, b;
bool mark[N];
int G[N][N], D[N];

inline void upd(int &x, int y){ if (x>y) x=y;}

int main(){
	ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	memset(G, 63, sizeof(G));
	cin>>n>>m;
	while (m--){
		cin>>u>>v>>x;
		G[u][v]=min(G[u][v], x);
	}
	for (int i=0; i<n; i++){
		memset(D, 63, sizeof(D));
		memset(mark, 0, sizeof(mark));
		for (int v=0; v<n; v++) upd(D[v], G[i][v]);
		while (1){
			int v=-1;
			for (int x=0; x<n; x++) if (!mark[x]){
				if (v==-1 || D[v]>D[x]) v=x;
			}
			if (v==-1) break ;
			mark[v]=1;
			upd(D[(v+1)%n], D[v]+1);
			for (int u=0; u<n; u++)
				upd(D[(u+D[v])%n], D[v]+G[v][u]);
			
		}
		for (int j=0; j<n; j++){
			if (i==j) cout<<"0 ";
			else cout<<D[j]<<" ";
		}
		cout<<"\n";
	}
	
	return 0;
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值