题目大意
有N个点,每个点有一个权值。在这N个点间有M条有向边,每条边的长度等于 ( 终 点 权 值 − 起 点 权 值 ) 3 (终点权值 - 起点权值)^3 (终点权值−起点权值)3。求1号点到其他点的距离。
输入
第一行输入 T,表明共有 T 组数据。(1 <= T <= 50)
对于每一组数据,第一行输入 N,表示点的个数。(1 <= N <= 200)
第二行输入 N 个整数,表示 1 ~ N 点的权值 a[i]。(0 <= a[i] <= 20)
第三行输入 M,表示有向边的条数。(0 <= M <= 100000)
接下来 M 行,每行有两个整数 A B,表示存在一条 A 到 B 的有向边。
接下来给出一个整数 Q,表示询问个数。(0 <= Q <= 100000)
每一次询问给出一个 P,表示求 1 号点到 P 号点的最小距离。
输出
每个询问输出一行,如果不可达或距离小于 3 则输出 ‘?’
基本思路
本题很显然是一个最短路问题,其难点在于存在负边,进而可能出现负环,我们要排除负环的干扰,并求得1号点到其他点的最短路长度。因为存在负边权,所以不能使用Dijkstra算法。
一种可行的方法是使用spfa算法。但是问题在于,负环会导致一些点被无限制的松弛下去,从而使得算法陷入死循环。为此,我们可以采用一个数组cnt,cnt[v]表示1号点到点v的最短路包含的点数(最短路长度+1),如果cnt[v]超过了总点数N,说明v在负环上或者可由负环上的点到达,这是给v打上标记(neg[v]=1),阻止v入队。
另外,一旦v在负环上或者可由负环上的点到达,那么可由v到达的点也会被无限制的松弛(如果不做处理),导致距离变为负无穷,因此这些点也需要被处理掉,可以通过从v点进行深度优先搜索dfs(v)来找到这些点,并打上标记neg,从而阻止这些点入队。
完整代码
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define inf 999999999
vector<pair<int, int> > G[205];
int T, N, M;
int dis[205], cnt[205], inq[205], a[205], neg[205];
void init()
{
for(int i=0;i<=205;i++)
{
G[i].clear();
cnt[i] = inq[i] = neg[i] = 0;
dis[i] = inf;
}
}
void read()
{
cin>>N;
for(int i=1;i<=N;i++) cin>>a[i];
cin>>M;
int A, B;
for(int i=1;i<=M;i++)
{
cin>>A>>B;
G[A].push_back({B, (a[B]-a[A])*(a[B]-a[A])*(a[B]-a[A])});
}
}
void dfs(int v)
{
if(!neg[v])
{
neg[v]=1;
for(int i=0, s=G[v].size();i<s;i++) dfs(G[v][i].first);
}
}
void spfa()
{
queue<int> Q;
inq[1] = 1;
dis[1] = 0;
cnt[1] = 1;
Q.push(1);
while(!Q.empty())
{
int u = Q.front(); Q.pop(); inq[u] = 0;
for(int i=0, s=G[u].size();i<s;i++)
{
int v = G[u][i].first;
int w = G[u][i].second;
if(!neg[v] && dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1;
if(cnt[v] > N) dfs(v);
else if(!inq[v])
{
inq[v] = 1;
Q.push(v);
}
}
}
}
}
void query(int Case)
{
int Q;
cin>>Q;
int p;
cout<<"Case "<<Case<<":"<<endl;
for(int i=1;i<=Q;i++)
{
cin>>p;
if(neg[p] || dis[p] < 3 || dis[p] == inf) cout<<"?"<<endl;
else cout<<dis[p]<<endl;
}
}
int main ()
{
cin>>T;
for(int i=1;i<=T;i++)
{
init();
read();
spfa();
query(i);
}
return 0;
}