Luogu5304 旅行者 - 二进制划分 - 最短路 -
题目链接:https://www.luogu.com.cn/problem/P5304
题解:
给一张图和一个点集 \({V}\),问点集内两两最短路的最小值
考虑将集合进行若干次划分:
每次对于一个二进制位 \(i\),如果 \(V_i\) 该位为 1 ,那么就划到第一个集合,否则划到第二个集合
然后求第一个集合中的点和第二个集合中的点最短路的最小值即可。具体地,可以建一个 \(S \rightarrow S_1\),再建一个 \(S_2 \rightarrow T\),求 \(S\rightarrow T\) 的最小值即可
注意原问题中的点集的任意两个点,因为必然有至少一个二进制位不同,所以肯定存在某一种划分,使得这两个点分到不同集合,从而能包含在答案中,这样我们就形成了一个满射,从而证明了方法的正确性
时间复杂度 \(O(n\log^2 n)\),需要 O2
代码:
// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back
using namespace std;
typedef long long ll;
typedef long long LL;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 1e5+5;
int n,m,k;
int suki[maxn];
vector<pii>o[maxn],g[maxn];
int s,t;
int dis[maxn],vis[maxn];
void dijkstra(){
priority_queue<pii>pq;
memset(dis,0x3f,sizeof dis);
memset(vis,0, sizeof vis);
dis[s] = 0;pq.push(mpr(0, s));
while(!pq.empty()){
pii now = pq.top(); pq.pop();
int ds = -now.first, x = now.second;
if(vis[x])continue;
vis[x] = 1;
for(pii nn : g[x]){
int u = nn.first;
if(dis[x] + nn.second < dis[u]){
dis[u] = dis[x] + nn.second;
pq.push(mpr(-dis[u], u));
}
}
}
}
void solve(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
if(x == y)continue;
o[x].pb(mpr(y,z));
}
for(int i=1;i<=k;i++)scanf("%d",&suki[i]);
int ans = 1e9;
for(int cs=0;cs<=16;cs++){
for(int i=0;i<=n;i++)g[i] = o[i];
vector<int>s1,s2;
for(int i=1;i<=k;i++){
if((suki[i] >> cs) & 1)s1.pb(suki[i]);
else s2.pb(suki[i]);
}
s=0, t=n+1;
for(int it : s1)g[s].pb(mpr(it, 0));
for(int it : s2)g[it].pb(mpr(t, 0));
dijkstra();
ans = min(ans, dis[t]);
for(int it : s2)g[it].pop_back();
for(int it : s1)g[s].pop_back();
for(int it : s2)g[s].pb(mpr(it, 0));
for(int it : s1)g[it].pb(mpr(t, 0));
dijkstra();
ans = min(ans, dis[t]);
}
cout << ans << '\n';
for(int i=1;i<=n;i++)o[i].clear();
}
signed main(){
// freopen("Luogu5304.in","r",stdin);
int te;scanf("%d",&te);
while(te--)solve();
return 0;
}