solution-p3096
题解 P3096 【[USACO13DEC]Vacation Planning G】
题意
给定一个有\(n\)个点,\(m\)条边的有向简单图,其中有\(k\)个点是枢纽,并告诉你这\(k\)个点。
\((1\le n,m\le 20000\),\(1\le k\le 200)\)
给定\(1\le q\le 50000\)个询问,第\(i\)个询问给出\(u_i\)和\(v_i\),求
\(\large\sum\limits_{i=1}^q{pos(u_i,v_i)}\quad\)及\(\quad\large\sum\limits_{i=1}^q{dis(u_i,v_i)}\)
其中,当\(u\)可以经过至少一个枢纽到达\(v\)时,\(pos(u,v) = 1\)
否则\(pos(u,v) = 0\);
而当\(pos(u,v) = 0\)时,\(dis(u,v) = 0\)
否则\(dis(u,v)\)为u经过至少一个枢纽到达v的最短路径长度。
怎么这么绕啊
思路
显然跑\(n^3\)的Floyd是会妥妥的T飞的。让我们换个角度思考。
注意到\(k\le 200\)十分好欺负,那么我们想怎么搞出一个依赖\(k\)的算法。
参考这道题的菜鸡版本P3094,我们可以枚举枢纽来计算最短路
因此我们不用计算出每两点间的最短路,只需计算:
(1)所有点到所有枢纽之间的最短路。
(2)所有枢纽到所有点之间的最短路。
想要计算(1),我们可以建一个反向图,对所有枢纽跑一遍dijkstra;
计算(2)则只需对正向图中的所有枢纽跑一遍dijkstra。
最后枚举枢纽,统计一下即可。
小技巧:建边的时候每建一条\((u,v)\)的边我们可以同时建一条\((v+n,u+n)\)的边作为反向边,这样就避免了开两个数组。
代码
// 此处应有头文件...
const int N = 4e4 + 10,M = 2e2 + 10;
const int inf = 0x3fffffff;
int n,m,s,k,q; // s为源点
int cnt = 0;
int st[N][M]; // 记录所有点到所有枢纽之间的最短路
int ed[M][N]; // 记录所有枢纽到所有点之间的最短路
int mid[M]; // 枢纽
int dis[N];
bool vis[N];
int head[N];
struct edge
{
int v,w;
int nxt;
}edge[N];
struct node
{
int dis,num;
bool operator < (const node &x) const
{return x.dis < dis;}
};
void add(int u , int v , int w)
{
++cnt;
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].nxt = head[u];
head[u] = cnt;
return ;
}
void dijkstra()
{
for(int i = 1;i <= n << 1;i++) // 循环开两倍,同时初始化反向边域
dis[i] = inf;
memset( vis , false , sizeof(vis) );
dis[s] = 0;
pr_q<node> q;
q.push( (node){0 , s} );
while( !q.empty() )
{
int u = q.top().num;
q.pop();
if( vis[u] )
continue;
vis[u] = true;
for(int i = head[u];i;i = edge[i].nxt)
{
int v = edge[i].v;
if(dis[v] > dis[u] + edge[i].w) // relax
{
dis[v] = dis[u] + edge[i].w;
q.push( (node){dis[v] , v} );
}
}
}
}
int main()
{
cin >> n >> m >> k >> q;
int u,v,w;
for(int i = 1;i <= m;i++)
cin >> u >> v >> w,add(u , v , w),add(v + n , u + n , w); // 反向边
for(int i = 1;i <= k;i++)
{
cin >> mid[i];
s = mid[i] + n; // 对反向图跑dijkstra.
dijkstra();
for(int j = n + 1;j <= n << 1;j++) // 别忘了从n+1开始
if(dis[j] != inf)
st[j - n][i] = dis[j];
else
st[j - n][i] = -1; // 这里赋成-1是为了处理询问时u或v就是枢纽的情况
s = mid[i]; // 对正向图跑dijkstra.
dijkstra();
for(int j = 1;j <= n;j++)
if(dis[j] != inf)
ed[i][j] = dis[j];
else
ed[i][j] = -1;
}
int ans = 0,c = 0; // 最短路长度和,可到达的数量
while(q--)
{
int minn = inf;
cin >> u >> v;
for(int i = 1;i <= k;i++)
if( st[u][i] != -1 && ed[i][v] != -1 )
minn = min(st[u][i] + ed[i][v] , minn);
if(minn != inf)
++c,ans += minn;
}
cout << c << endl << ans << endl;
return 0;
}
时间复杂度\(O(nklogn+qk)\),不过听说这题SPFA跑的比dijkstra快
故而要开O2,不然妥妥的T飞