[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;
}
洛谷数据需要滚动数组优化,正在写.