建立虚点?一类暴力连边会超时的最短路问题
最近在牛客和cf上面一连做到了两道类似的题目,是关于最短路算法优化问题的。
https://ac.nowcoder.com/acm/contest/6885/E
题目大意:
给定 个点,第 个点有权值 。如果对于 有 不为 ,那么 间有无向边,边权为 。问从 到 的最短路。
首先暴力连边的做法肯定是不行的,由于题目条件的原因,我们可以往进制方面去想。
依次考虑每一个位,并且对当前位建立一个新点,设为 。遍历所有点,如果点 满足这一位上为 ,那么连一条从 到 的边,边权为这一位对应的二进制数。
这样我们能够建立32个虚点,通过这些虚点,我们变相的达成了点与点之间连线,并且还能统计答案的任务。
#include <bits/stdc++.h> #define debug freopen("r.txt","r",stdin) #define mp make_pair #define ri register int #define pb push_back #define lb(x) (x&(-x)) using namespace std; typedef long long ll; typedef unsigned long long ull; typedef double lf; typedef pair<ll, ll> pii; const int maxn = 1e6+35; const int N = 1500+10; const ll INF = 0x3f3f3f3f3f3f3f3f; const int mod = 1e9+7; const int hash_num = 131; const double eps=1e-6; const double PI=acos(-1.0); inline ll read(){ll s=0,w=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();} while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar(); return s*w;} ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;} struct node{ int to; ll dis; bool operator<(const node &b)const{ return dis>b.dis; } }; int n,t,a[maxn]; bool vis[maxn]; vector<int> v[maxn]; ll dis[maxn]; void dij(int s){ //s是起点,dis是结果 memset(vis,0,sizeof(vis)); memset(dis,INF,sizeof(dis)); dis[s]=0; //last[s]=-1; static priority_queue<node> q; q.push({s,0}); while(!q.empty()){ int u=q.top().to; q.pop(); if(vis[u])continue; vis[u]=1; for (ll j = 0; j < 32; ++j) if ((a[u]>>j)&1) { for(auto p:v[j]) { if(dis[p]>dis[u]+(1ll<<j)){ dis[p]=dis[u]+(1ll<<j); q.push({p,dis[p]}); //last[p]=x; //last可以记录最短路(倒着) } } v[j].clear(); } } } int main() { #ifndef ONLINE_JUDGE debug; #endif t=read(); while (t--) { n=read(); for (int i = 0; i < 32; ++i) v[i].clear(); for (int i = 1; i <= n; ++i) { a[i] =read(); } for (int i = 1; i <= n; ++i) { for (ll j = 0; j < 32; ++j) { if ((a[i]>>j)&1) v[j].pb(i); } } dij(1); if (dis[n] == INF) cout << "Impossible" <<endl; else cout << dis[n] <<endl; } return 0; }
https://codeforces.com/contest/1486/problem/E
题目大意:
给定 n 个点,m条边,如果从a城市,到b城市,再到c城市,花费,问从 1 到 1~n 的最短路。
如果直接将两条边合成为一条边来重构图还是不行,注意到题目条件,边的价值很小,我们可以往价值方面去想。
可以将每个点按照边权拆成51个虚点,编号0~50。对于每条边,如果边权为w,则从一个点的0号点连到另一个点的w号节点
所以,边的个数就变成了 2m ,套上最短路模板跑即可得到答案。
#include <bits/stdc++.h> #define debug freopen("r.txt","r",stdin) #define mp make_pair #define ri register int #define pb push_back using namespace std; typedef long long ll; typedef unsigned long long ull; typedef double lf; typedef pair<ll, ll> pii; const int maxn = 1e7+10; const int N = 1500+10; const int INF = 0x3f3f3f3f; const int mod = 1e9+7; const int hash_num = 131; const double eps=1e-6; const double PI=acos(-1.0); inline ll read(){ll s=0,w=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();} while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar(); return s*w;} ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;} struct node{ int to; int dis; bool operator<(const node &b)const{ return dis>b.dis; } }; int n,m,u,v; bool vis[maxn]; int dis[maxn],w; vector<node> a[maxn]; void dij(int s){ //s是起点,dis是结果 fill(vis,vis+n*51+1,0); fill(dis,dis+n*51+1,INF); dis[s]=0; //last[s]=-1; static priority_queue<node> q; q.push({s,0}); while(!q.empty()){ int x=q.top().to; q.pop(); if(vis[x])continue; vis[x]=1; for(auto i:a[x]){ int p=i.to; if(dis[p]>dis[x]+i.dis){ dis[p]=dis[x]+i.dis; q.push({p,dis[p]}); //last[p]=x; //last可以记录最短路(倒着) } } } } int main() { #ifndef ONLINE_JUDGE debug; #endif n=read(),m=read(); for (int i = 1; i <= m; ++i) { u=read(),v=read(),w=read(); u--,v--; a[u*51].pb({v*51+w,0}); for (int j = 1; j <= 50; ++j) { a[u*51+j].pb({v*51,(j + w) * (j + w)}); } a[v*51].pb({u*51+w,0}); for (int j = 1; j <= 50; ++j) { a[v*51+j].pb({u*51,(j + w) * (j + w)}); } } dij(0); for (int i = 0; i < n ; ++i) { if (dis[i*51] == INF) cout << -1 << " "; else cout << dis[i*51] << " "; } cout <<endl; return 0; }
两个题目都是暴力连边会超时,但是通过题目的另一些条件,都可以建立虚点,从而降低复杂度。