[TK] Tourist Attractions

题目描述

给出一张有 \(n\) 个点 \(m\) 条边的无向图,每条边有边权。

你需要找一条从 \(1\)\(n\) 的最短路径,并且这条路径在满足给出的 \(g\) 个限制的情况下可以在所有编号从 \(2\)\(k+1\) 的点上停留过。

每个限制条件形如 \(r_i, s_i\),表示停留在 \(s_i\) 之前必须先在 \(r_i\) 停留过。

注意,这里的停留不是指经过

解法分析

对于这道题的状压. 我们考虑枚举 "现在已经在哪些点停留" 这样一种状态. 然后去寻找每一个当前未停留的点,考虑这个点的前置节点是否全部已经停留,如果是,那么枚举每一个已在集合内的节点,尝试把这个点通过某条边放入集合内,进行状态转移.

那么我们需要进行状态压缩的有两个东西: 现在已有的点的情况 (用于枚举) 和每个点的前置节点情况 (用于判断).

最后需要我们输出的就是在全部节点停留情况下的状态.

这道题的主要思路:

for(int i=1;i<=(1<<k)-1;++i){
//all possible chance
	for(int j=0;j<=k-1;++j){ //node
		if(i&(1<<j)){
		//if this node in this chance
			for(int hdk=0;hdk<=k-1;++hdk){ 
			//then try to add a node
				if(!(i&(1<<hdk))&&((i|r[hdk+2])==i)){
				//if find a node not in chance and can be placed
					update();
				} 
				//then do the change. why it's +2 is because I store 3 in position 1.(1 and 2 is no need)
			}
		}
	}
}

那么,为了更新已选中的点的距离,我们需要知道从任意点到另一点的距离,也就是跑一边全源最短路.

我们定义 \(dis[i][j]\) 为全源最短路下的 \(i,j\) 最短距离. \(r[i]\) 表示 \(i\) 的全部前置节点的状压表示. \(f[i][j]\) 表示在已经选择 \(i\) (状压表示) 这些节点的情况下,且最后一个选中的节点为 \(j\) 的最短路径长度. 那么我们有:

\[f[i\ add\ k][k]=min(f[i\ add\ k][k],min\sum^{j}_{j\in i}(f[i][j]+dis[j][k])) \]

那么我们怎么表示 \(i\ add\ k\) 呢. 其实只需要将 \(i\) 中的 \(k\) 点的位置置为 \(1\). 也就是做一次或运算.

这题我也不知道它卡什么. 我存图的 vector 滚动数组会比前向星小很多,而 DIJ 又比 SPFA 快很多. 总之按对的来吧.

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
struct edge{
	int to,w;
};
struct node{
	int id,dis;
	bool operator<(const node A)const{
		return dis>A.dis;
	}
};
priority_queue<node> q;
vector<edge> e[20001];
int dis[31][20001]; //root i's dis j
bool vis[20001];
void dij(int s){
	memset(vis,false,sizeof vis);
	for(int i=1;i<=n;++i){
		dis[s][i]=1000000000;
	}
	while(!q.empty()) q.pop();
	dis[s][s]=0;
	q.push(node{s,0});
	while(!q.empty()){
		node u=q.top();
		q.pop();
		if(vis[u.id]) continue;
		vis[u.id]=true;
		for(edge i:e[u.id]){
			if(dis[s][i.to]>dis[s][u.id]+i.w){
				dis[s][i.to]=dis[s][u.id]+i.w;
				q.push(node{i.to,dis[s][i.to]});
			}
		}
	}
}
int f[1<<20][31],r[31],ans=1000000000; //1=zt(which nodes been chose) 2=node //r= mustfore
int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=m;++i){
		int a,b,c;
		cin>>a>>b>>c;
		e[a].push_back(edge{b,c});
		e[b].push_back(edge{a,c});
	}
	if(k==0){
		dij(1);
		cout<<dis[1][n];
		return 0;
	}
	int q;
	cin>>q;
	while(q--){
		int a,b;
		cin>>a>>b;
		r[b]+=(1<<(a-2));//
	}
	for(int i=1;i<=k+1;++i){
		dij(i);
	}
	for(int i=0;i<=(1<<k)-1;++i){
		for(int j=1;j<=k+1;++j){
			f[i][j]=1000000000;
		}
	}
	f[0][1]=0;
	for(int i=2;i<=k+1;++i){
		if(!r[i]){ //if this point hasn't any requires.
			f[1<<(i-2)][i]=dis[1][i];
		}
	}
	for(int i=1;i<=(1<<k)-1;++i){ //all possible chance
		for(int j=0;j<=k-1;++j){ //node
			if(i&(1<<j)){  //if this node in this chance
				for(int hdk=0;hdk<=k-1;++hdk){  //then try to add a node
					if(!(i&(1<<hdk))&&((i|r[hdk+2])==i)){ //if find a node not in chance and can be placed
						f[i|(1<<hdk)][hdk+2]=min(f[i|(1<<hdk)][hdk+2],f[i][j+2]+dis[j+2][hdk+2]);
					} //then do the change. why it's +2 is because I store 3 in position 1.(1 and 2 is no need)
				}
			}
		}
	}
	for(int i=2;i<=k+1;++i){
		ans=min(ans,f[(1<<k)-1][i]+dis[i][n]);
	}
	cout<<ans;
}

洛谷数据需要滚动数组优化,正在写.

image

posted @ 2024-04-03 11:43  HaneDaniko  阅读(28)  评论(1编辑  收藏  举报