【题解 && 海亮集训 && 图论】 搬迁Relocation

博客围绕FJ农场选址问题展开,该区域有N个城镇、M条双向道路,K个城镇有市场,FJ要选非市场城镇建农场,使每天光顾K个市场并返回的行程最小。解题从K入手,先求K次单源最短路,预处理经过K个点的顺序,再枚举非标记城市求最小路径值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

题目描述:

FJ决定搬家,重新建设农场,以便最小化他每天的行程。

FJ搬往的区域有N(1 <= N <= 10,000)个城镇,共有M (1 <= M <= 50,000)条双向道路连接某些城镇,所有城镇都能找到互通路线。

有K (1 <= K <= 5)个城镇建有市场,FJ每天离开新农场后,都要光顾这K个城镇,并返回农场。FJ希望建设农场的城镇不包含市场。

请帮助FJ选择最佳城镇建设农场,使得他每天的行程最小。


solution:

这道题的n值比较大,但是k却只有5,我们可以从k入手。

题目中是需要让我们求一个点,让这个点以此经过k个特殊点,所经过的路径之和最小。
从题目中的k入手,我们先求的这k次单源最短路,求出每个点到这k个点的最短距离。

之后用 O ( n ! ) O(n!) O(n)的时间预处理出经过这k个点的顺序。
然后就简单,枚举每一个除了标记的城市,求出当前城市作为起点的最小路径值(这时就可以用预处理出的数组来固定经过k的顺序,取最小值),取出所有的最小值即可。

代码稍微有点长,不过思路非常好理解


Code

#include<bits/stdc++.h>
using namespace std;
typedef pair < int , int > pii;
#define mp make_pair
struct node{
    int Next,y,v;
}e[1010100];
vector < int > Q[150];
int len=0,n,m,k,num=0,cnt=0,minn=100000000000;
int linkk[1011001];
int K[10101010];
int d[6][1010101];
int b[6];
bool f[6];
bool isk[100010];
bool vis[100010];

void insert(int x,int y,int v){
	e[++len].Next=linkk[x];
	linkk[x]=len;
	e[len].y=y;
	e[len].v=v;
}

void find_shortest(int id){
	int x=K[id];
    priority_queue < pii , vector < pii > , greater < pii > > q;
	memset(vis,0,sizeof(vis));
	q.push(mp(0,x));
	d[id][x]=0;
	while (!q.empty()){
	    int xx=q.top().second,vv=q.top().first;
	    q.pop();
	    if (vis[xx])continue;
	    vis[xx]=1;
	    for (int i=linkk[xx];i;i=e[i].Next)
	      if (d[id][xx]+e[i].v<d[id][e[i].y])
	        d[id][e[i].y]=d[id][xx]+e[i].v,q.push(mp(d[id][e[i].y],e[i].y));
	}
}

void add(){
    ++cnt;
    for (int i=1;i<=k;i++) Q[cnt].push_back(b[i]);
}

void dfs(int t){
    if (t>k) {add();return;}
    for (int i=1;i<=k;i++)
      if (!f[i]){
	      b[++num]=i;
	      f[i]=1;
	      dfs(t+1);
	      b[num--]=0;
	      f[i]=0;
	  }
}

int ask(int st,int x){
	int ans=0,last=st,now=Q[x][0];
	ans+=d[now][st];
	for (int i=1;i<Q[x].size();i++){
	    last=now,now=Q[x][i];
	    ans+=d[last][K[now]];
	}
	ans+=d[now][st];
	return ans;
}//依次经过的权值和

int main(){
//	freopen("1.in","r",stdin);
//	freopen("1.out","w",stdout);
    scanf("%d %d %d",&n,&m,&k);
    for (int i=1;i<=k;i++) scanf("%d",&K[i]),isk[K[i]]=1;//标记k城市
    for (int i=1,x,y,z;i<=m;i++){
	    scanf("%d %d %d",&x,&y,&z);
	    insert(x,y,z),insert(y,x,z);
	}
	memset(d,20,sizeof(d));
	for (int i=1;i<=k;i++){
	  find_shortest(i);//K次最短路
   }
	dfs(1);//预处理
	for (int i=1;i<=n;i++){
	    if (isk[i]) continue;
	    for (int j=1;j<=cnt;j++) minn=min(minn,ask(i,j));
	}
	printf("%d",minn);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值