建立虚点?一类暴力连边会超时的最短路问题

最近在牛客和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;
}
View Code

 

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;
}
View Code

 

 


两个题目都是暴力连边会超时,但是通过题目的另一些条件,都可以建立虚点,从而降低复杂度。

posted @ 2021-02-23 17:29  Y-KnightQin  阅读(118)  评论(0编辑  收藏  举报