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飞

posted @ 2024-03-06 16:55  iorit  阅读(19)  评论(0)    收藏  举报