Nearest Maintenance Point CSU - 2005 (堆优化的dijstar + bitset容器的位运算)

A county consists of n cities (labeled 1, 2, …, n) connected by some bidirectional roads. Each road connects a pair of distinct cities. A robot company has built maintenance points in some cities. Now, they need your help to write a program to query the nearest maintenance point to a specific city.

Input

There will be at most 200 test cases. Each case begins with four integers n, m, s, q(2 ≤ n ≤ 104, 1 ≤ m ≤ 5 × 104, 1 ≤ s ≤ min{n, 1000},1 ≤ q ≤ min{n, 1000}), the number of cities, the number of roads, the number of cities which have maintenance points and the number of queries. Then m lines contain the descriptions of roads. Each of them contains three integers ui, vi, wi(1 ≤ ui, vi ≤ n, ui ≠ vi, 1 ≤ wi ≤ 1000), denoting there is a road of length wi which connects city ui and vi. The next line contains s integers, denoting the cities which have maintenance points. Then qlines contain the descriptions of queries. Each of them contains one integer denoting a specific city. The size of the whole input file does not exceed 6MB.

Output

For each query, print the nearest city which has a maintenance point. If there are multiple such cities, print all of them in increasing order separated by a single space. It is guaranteed that there will be at least one such city.

Sample Input
4 4 2 3
1 2 1
2 3 2
2 4 1
4 3 2
1 3
3
2
4
Sample Output
3
1
1 3
Hint


题意:输出n,m,s,q,n为n个点,m为m条边,s为s个特殊点,q为q次询问,q每次询问一个点,输出离这个点最近的特殊点,要是有多个就按从小到大顺序输出;

思路,总不能来s次dijstar吧, 因为找的是离这个点最近的特殊点,我们可以设一个超级源点 0 ,再从超级源点到每一个特殊点连上一条边,边权为0,之间从超级源点 0开始 搜就行了,在这用了优先队列优化的dijstar,用到了c++中的容器bitset,记录到这点的特殊点,比bitset,二进制运算,有点类似于状压的感觉;

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#include<bitset>
#include<queue>

#define Max 10005
#define INF 0x3f3f3f3f
bitset<1005>ans[Max];
struct node
{
	int x,d;
	bool operator < (const node a) const
	{
		return a.d < d;
	}
};

struct edge
{
	int end,d;
}stu[Max*10+1000];         // 数组大小,因为边是双向的,开二倍; 

int frist[Max],next1[Max*10+1000];
int n,m,s,p;

bool vis[Max];  // 标记特殊点 
int dis[Max];    // 超级源点0到这一点的最短距离,也就是特殊点到这一点的最短距离; 
bool book[Max];
int ss[1005];   //存特殊点  
int sum;
void add_edge(int x,int y,int d)
{
	stu[sum].end = y;
	stu[sum].d = d;
	next1[sum++] = frist[x];
 	frist[x] = sum - 1; 
}

void init()
{
	memset(frist,-1,sizeof(frist));
	memset(vis,false,sizeof(vis));
	for(int i = 0;i<=n;i++)
		ans[i].reset();
}

void dijstar(int st)
{
	memset(dis,INF,sizeof(dis));
	memset(book,false,sizeof(book));
	dis[st] = 0;
	priority_queue<node >q;
	node star;
	star.x = st;
	star.d = 0;
	q.push(star);
	while(!q.empty())
	{
		star = q.top();
		q.pop();
		int t = star.x;
		if(book[t]) continue;    // 气死了,刚开始上面 让 t==frist[star.x],下面标记t了,标记成边了,
								// 怎么也没找到错,哎,邻接表用的不熟练; 
		book[t] =  true;
		for(int i = frist[t];i!=-1;i = next1[i])
		{
			if(dis[stu[i].end] > dis[star.x] + stu[i].d)
			{
				dis[stu[i].end] = dis[star.x] + stu[i].d;
				
				node end;
				end.x = stu[i].end;
				end.d = dis[stu[i].end];
				q.push(end);
				if(vis[stu[i].end]) continue;  // 如果第一次到到这个点时的距离为6,那么第二次为5的话, 
												// 那么 6 就不用 重复找最短路了
								 
				ans[stu[i].end].reset();       // ans[i].reset(),把容器ans[i]中的二进制全部变为0; 
				/*for(int j = 0;j<s;j++)	
				{
					if(ans[star.x][j])       // ans[i][j],判断bitset容器ans[i] 在j这个位置上的二进制是不是1; 
						ans[stu[i].end][j] = 1;
				}*/	
				ans[stu[i].end] = ans[star.x];	  // 大于,直接覆盖;		
			}
			else if(dis[stu[i].end] == dis[star.x] + stu[i].d)
			{
				/*for(int j = 0;j<s;j++)
				{
					if(ans[stu[i].star][j])		
					 ans[stu[i].end][j] = 1;		
				}*/
				ans[stu[i].end] |= ans[star.x];   // 相等的情况就有多个 或上; 
			}	
		}
	}
	// 于spfa 的区别,spfa 是用的队列,这个是优先队列, spfa判断这个点要是在队列中,就只更新到这个点的距离
	// 不加入队列,而这个堆优化的dijstar必须要加入队列,这个按到这点的距离排序的,第一次是到这个点的距离6,
	// 第二次 到这个点距离是5,那么一定加入队列;每一次取得都是距离起点最近的点,所以不没有出队列时,取消标记;
	// 而 spfa 出队列时要取消标记,因为取得不是离起点最近的点,spfa和dijstar更新的都是相邻边,spfa,第一次从这一点更新时
	// 这一点到起点的距离是6,更新了一次,从其他点到到这个点的距离是5,你要是出队列时,没有取消标记的话,那么就进不了队列了
	// 没法更新成最短的距离了,所以,spfa出队列时要取消标记;  
}

int main()
{
	int i,j;
	while(~scanf("%d%d%d%d",&n,&m,&s,&p))
	{
		init();
		int x,y,d;
		sum = 0;
		for(i = 0;i<m;i++)
		{
			scanf("%d%d%d",&x,&y,&d);
			add_edge(x,y,d);
			add_edge(y,x,d);	
		}
		for(i = 0;i < s ;i++)
			scanf("%d",&ss[i]);
		sort(ss,ss+s);
		for(i = 0;i<s;i++)
		{
			vis[ss[i]] = true;
			add_edge(0,ss[i],0);
			ans[ss[i]][i] = 1;
		}
		dijstar(0);
		for(i = 0;i<p;i++)
		{
			scanf("%d",&x);
			int flag = 0;
			for(j = 0;j < s;j++)
			{
				if(ans[x].test(j))    // 判断j这一位上是不是 1; 
				{
					if(flag) printf(" ");
					printf("%d",ss[j]);
					flag = 1;
				}
				
			}
			printf("\n");
		}
	}
	return 0;
} 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值