[ZOJ 2963] Treasure Hunter [最短路+状态压缩]

69 篇文章 0 订阅
8 篇文章 0 订阅

一个有500个节点的无向图,有至多15个节点上有宝藏,每次进入每个点的时候都有对应的花费,过某条路也有对应的花费。求从s到t,经过所有的宝藏点,的最小花费。

首先从s和每个宝藏点出发,共计求16次最短路。然后把原图缩小,只留s,t和宝藏点。这样就变成了求17个点的,从s到t的,经过所有其他点的最短路。直接状态压缩,然后用dijkstra求即可。

#include <cstdio>
#include <queue>

using namespace std;

inline bool update(int &a,int b) {
	if (a==-1||a>b) {
		a=b;
		return true;
	}
	return false;
}

bool haveTreasure[500];
int treasureIn[15];
int cost[500];
int edg[500][500];
int dp[17][32768];
int cost2[17][17];
int treasureN;

struct HeapNode {
	int i,v;
	HeapNode (int ii,int vv) {
		i=ii;v=vv;
	}
	friend bool operator < (const HeapNode &a,const HeapNode &b) {
		return a.v>b.v;
	}
};
void calCost(int s,int tar,int n,int ss,int tt) {
	static int v[500];
	static priority_queue<HeapNode> c;
	for (int i=0;i<n;i++) v[i]=-1;
	while (!c.empty()) c.pop();
	v[s]=0;
	c.push(HeapNode(s,0));
	while (!c.empty()) {
		int i=c.top().i,curv=c.top().v;
		c.pop();
		if (curv==v[i]) {
			for (int j=0;j<n;j++) {
				if (edg[i][j]!=-1) {
					if (update(v[j],curv+edg[i][j]+cost[j])) {
						c.push(HeapNode(j,v[j]));
					}
				}
			}
		}
	}
	for (int i=0;i<treasureN;i++) {
		cost2[tar][i]=v[treasureIn[i]];
	}
	cost2[tar][treasureN]=v[ss];
	cost2[tar][treasureN+1]=v[tt];
}
struct HeapNode2 {
	int i,j,v;
	HeapNode2 (int ii,int jj,int vv) {
		i=ii;j=jj;v=vv;
	}
	friend bool operator < (const HeapNode2 &a,const HeapNode2 &b) {
		return a.v>b.v;
	}
};
void calDP() {
	int n=treasureN+2;
	static priority_queue<HeapNode2> c;
	while (!c.empty()) c.pop();
	dp[n-2][0]=0;
	c.push(HeapNode2(n-2,0,0));
	while (!c.empty()) {
		int i=c.top().i,v=c.top().v,j=c.top().j;
		c.pop();
		if (v==dp[i][j]) {
			for (int k=0;k<n;k++) {
				if (cost2[i][k]!=-1) {
					int newj=(j|1<<k)&(1<<n-2)-1;
					if (update(dp[k][newj],v+cost2[i][k])) {
						c.push(HeapNode2(k,newj,dp[k][newj]));
					}
				}
			}
		}
	}
}

int main() {
	int i,j,n,s,t,x,y,z,m;
	while (scanf("%d",&n)!=EOF) {
		for (i=0;i<n;i++) scanf("%d",&cost[i]);
		for (i=0;i<n;i++) haveTreasure[i]=false;
		scanf("%d",&t);
		for (i=0;i<t;i++) {
			scanf("%d",&x);
			haveTreasure[x-1]=true;
		}
		treasureN=0;
		for (i=0;i<n;i++) {
			if (haveTreasure[i]) {
				treasureIn[treasureN++]=i;
			}
		}
		for (i=0;i<n;i++)
			for (j=0;j<n;j++)
				edg[i][j]=-1;
		scanf("%d",&m);
		for (i=0;i<m;i++) {
			scanf("%d%d%d",&x,&y,&z);
			update(edg[x-1][y-1],z);
			update(edg[y-1][x-1],z);
		}
		scanf("%d%d",&s,&t);
		s--;t--;
		for (i=0;i<treasureN+2;i++)
			for (j=0;j<treasureN+2;j++)
				cost2[i][j]=-1;
		calCost(s,treasureN,n,s,t);
		for (i=0;i<treasureN;i++) {
			calCost(treasureIn[i],i,n,s,t);
		}
		/*
		for (i=0;i<treasureN+2;i++) {
			for (j=0;j<treasureN+2;j++)
				printf("%d ",cost2[i][j]);
			printf("\n");
		}
		*/
		m=1<<treasureN;
		for (i=0;i<treasureN+2;i++)
			for (j=0;j<m;j++)
				dp[i][j]=-1;
		calDP();
		printf("%d\n",dp[treasureN+1][m-1]+cost[s]);
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值