题解 | #I Wanna Go Home#

I Wanna Go Home

http://www.nowcoder.com/practice/0160bab3ce5d4ae0bb99dc605601e971

“中间相遇攻击”是非常有效的降低时间复杂度的方法 暴力Dijkstra的优化

#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
#include <climits>
#include <vector>
#include <map>

//一开始把每条跨越边轮流放进去Dijkstra,算法超时

//优化:改成“中间相遇” 

using namespace std;

const int MAXN = 600 + 10;

const int MAXM = 10000 + 10;

const int INF = INT_MAX;

class Edge{
public:
	int from;
	int to;
	int length;
	int number;
	Edge(int f, int t, int l, int n):from(f),to(t),length(l),number(n){}
	bool operator<(const Edge &e) const {
		return length > e.length;
	}
};

class Point{
public:
	int number;
	int distance;
	Point(int n, int d):number(n),distance(d){}
	bool operator<(const Point &p) const {
		return distance > p.distance;
	}
};


vector<Edge> graph[MAXN];

vector<Edge> anti;//存连接两个阵营的边的容器 

int dis[MAXN];

int side[MAXN];

int valid[MAXM];//存边的有效性 此边是否激活使用

void Dijkstra(int x){
	priority_queue<Point> q;
	dis[x] = 0;
	q.push(Point(x, dis[x]));
	while(!q.empty()){
		int u = q.top().number;
		q.pop();
		for(int i=0; i<graph[u].size(); i++){//定位到一条边
			if(!valid[graph[u][i].number]){
				continue;
			}
			int t = graph[u][i].to;
			int l = graph[u][i].length;
			if(dis[t] > dis[u] + l){
				dis[t] = dis[u] + l;
				q.push(Point(t, dis[t]));
			}
		}
	}
	return;
}


int main(){
	int n, m;
	while(scanf("%d%d", &n, &m) != EOF){
		fill(dis, dis+n+1, INF);
		memset(graph, 0, sizeof(graph));
		for(int i=0; i<m; i++){ //输入所有边
			int from, to, length;
			scanf("%d%d%d", &from, &to, &length);
			graph[from].push_back(Edge(from, to, length, i));
			graph[to].push_back(Edge(to, from, length, i));
			valid[i] = 1;
		}
		for(int i=1; i<=n; i++){//输入点的阵营数据
			scanf("%d", &side[i]);
		}
		for(int i=1; i<=n; i++){
			for(int j=0; j<graph[i].size(); j++){//定位到一条边 
				if(side[i] != side[graph[i][j].to]){//是连接不同阵营的边 
					anti.push_back(graph[i][j]);
					valid[graph[i][j].number] = 0;//让这条边熄灭
				}
			}
		}
		//至此,得到了anti 
		//注意Edge是只存to的 这些to足够囊括端点,因为双向 但是会有重复(虽然后面我又决定存from了)
		vector<int> side1; //阵营1中与阵营2相连的点
		vector<int> side2; //阵营2中与阵营1相连的点
		priority_queue<int, vector<int>, greater<int> > qside1;
		priority_queue<int, vector<int>, greater<int> > qside2;
		for(int i=0; i<anti.size(); i++){//把“边境结点”分类
			int t = anti[i].to;
			if(side[t] == 1){
				qside1.push(t);
			}else if(side[t] == 2){
				qside2.push(t);
			}
		}
        //以下做的事是把两个优先队列中分好类的结点放进对应vector中
        //之所以要这么迂回是要让最终分类结果中没有重复元素
		int ttmp = -1;
		while(!qside1.empty()){
			if(ttmp != qside1.top()){
				side1.push_back(qside1.top());
			}
			ttmp = qside1.top();
			qside1.pop();
		}
		ttmp = -1;
		while(!qside2.empty()){
			if(ttmp != qside2.top()){
				side2.push_back(qside2.top());
			}
			ttmp = qside2.top();
			qside2.pop();
		}
		//此时完成了结点分类 
		//接下来把所有阵营2内部的边置0 
		for(int i=1; i<=n; i++){
			for(int j=0; j<graph[i].size(); j++){//定位到一条边 
				if(side[i] == 2 && side[graph[i][j].to] == 2){//是阵营2内部的边 
					valid[graph[i][j].number] = 0;
				}
			}
		}
		Dijkstra(1);
		map<int,int> one2side;//从阵营1到边境结点的距离
		for(int i=0; i<side1.size(); i++){
			one2side[side1[i]] = dis[side1[i]];
		}
		fill(dis, dis+n+1, INF);
		//接下来把所有阵营1内部的边置0 
		for(int i=1; i<=n; i++){
			for(int j=0; j<graph[i].size(); j++){//定位到一条边 
				if(side[i] == 2 && side[graph[i][j].to] == 2){//是阵营2内部的边 
					valid[graph[i][j].number] = 1;
				}else if(side[i] == 1 && side[graph[i][j].to] == 1){//是阵营1内部的边 
					valid[graph[i][j].number] = 0;
				}
			}
		}
		Dijkstra(2);
		map<int, int> two2side;//从阵营2到边境结点的距离
		for(int i=0; i<side2.size(); i++){
			two2side[side2[i]] = dis[side2[i]];
		}
		priority_queue<int, vector<int>, greater<int> > ans;
		for(int i=0; i<anti.size(); i++){//把“异国边”一条条加进去,各自计算结果
			int tmp;
			if(side[anti[i].from] == 1){
				if(one2side[anti[i].from] == INF || two2side[anti[i].to] == INF){
					continue;
				}
				tmp = anti[i].length + one2side[anti[i].from] + two2side[anti[i].to];
			}else{
				if(one2side[anti[i].to] == INF || two2side[anti[i].from] == INF){
					continue;
				}
				tmp = anti[i].length + one2side[anti[i].to] + two2side[anti[i].from];
			}
			ans.push(tmp);
		}
		//以下暴力做法是超时的  决定把anti还是改成vector 
//		while(!anti.empty()){//暴力,一条条加进去试
//			fill(dis, dis+n+1, INF);
//			Edge tmp = anti.top();
//			valid[tmp.number] = 1;
//			Dijkstra(1);
//			ans.push(dis[2]);
//			valid[tmp.number] = 0;
//			anti.pop();
//		}
		int anstmp;
		if(ans.empty()){
			anstmp = -1;
		}else{
			anstmp = ans.top();
		}
		printf("%d\n",anstmp);
		anti.clear();
	}
	return 0;
}
全部评论

相关推荐

头像
11-09 12:17
清华大学 C++
out11Man:小丑罢了,不用理会
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务