最短路的简单进阶(建图的优化)

25 篇文章 0 订阅
24 篇文章 0 订阅

这一篇描述了关于最短路的简单变式
对于最后一道题,真正复杂度通过的做法是拆点。
类似于网络流的拆点,不过这里的拆点,是为了使得处理边权信息得到可能
正常对点无法记录边权信息,我们可以考虑拆点将这样的信息暴露出来。
对于每个点发出的颜色,相当于可以独立出一个点。到达这个点只会可以通过这个点直接到达另一个点(不需要花费)。
在这里插入图片描述
有三个点 u , v , k u,v,k u,v,k,对于这三个点有两条边且颜色都是 c c c,如果处理只记录一次变化呢。
对于每个点发出的颜色都是一个新点 ( u , c ) , ( v , c ) , ( k , c ) (u,c),(v,c),(k,c) (u,c),(v,c),(k,c),这样子对于相邻边来说,一定是有公共节点的,如果相邻边颜色相同,公共节点发出的颜色相同的话,必然连接到一个节点,就能对到 k k k的节点实现更优。
最后的答案就是除以 2 2 2.
边权为0的颜色通道是便于瞬移来去除掉不必要的花费
十分巧妙。值得细细品味。

#inlucde<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

#define ll long long
#define eps 1e-6
#define inf 0x3f3f3f3f3f3f3f3f
const int maxn=505000;
const int maxm=1500000;

map<pair<int,int>,int>M;
int n,m,cnt=0;

struct Edge{
    int from,to;ll dist;
    Edge(){}
    Edge(int _from,int _to,ll _dist):from(_from),to(_to),dist(_dist){}
};
Edge ed[maxm];int he[maxn],ne[maxm],etop=1;
void insert(int u,int v,ll w){ed[etop]=Edge(u,v,w);ne[etop]=he[u];he[u]=etop++;}

ll d[maxn];
bool vis[maxn];
priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > q;
int dijkstra(int s,int t){
    memset(vis,0,sizeof(vis));
    memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    q.push(make_pair(0, s));
    while(!q.empty()){
        int now = q.top().second;
        q.pop();
        if(!vis[now]){
            vis[now] = true;
            for(int i = he[now]; i; i = ne[i]){
                Edge& e = ed[i];
                if(d[e.to] > d[now] + e.dist){
                    d[e.to] = d[now] + e.dist;
                    q.push(make_pair(d[e.to], e.to));
                }
            }
        }
    }
    return d[t]==inf?-2:d[t];
}

int get(int x,int z){
    if(M[make_pair(x,z)])return M[make_pair(x,z)];
    else M[make_pair(x,z)]=++cnt;
    return cnt;
}

int main(){
    cin>>n>>m;
    cnt=n;
    FOR(i,1,m){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        int tmpx=get(x,z),tmpy=get(y,z);
        insert(x,tmpx,1);insert(tmpx,x,1);
        insert(tmpx,tmpy,0);insert(tmpy,tmpx,0);
        insert(tmpy,y,1);insert(y,tmpy,1);
    }
    printf("%d\n",dijkstra(1,n)/2);
}

接下来介绍一道题:按顺序访问一定量节点,最少走多少路。
注意边数只有 m ≤ n + 30 m≤n+30 mn+30
最简单的做法的就是不断求两点间最短路。
更优秀的做法是:边数较少,对于一棵树我们可以用 l c a lca lca加前缀和 l o g log log级别的求出两点间最短距离,再判断每一条多余的边,对于一条多余的边,如果最短路不经过,那么不用考虑,经过了必须考虑。
判断是否经过比较困难,我们可以直接计算他们到两点之间的距离再加上边权,判断最小的即是答案。
对于这条边,从u->左边->右边->v,当然也有可能反过来。所以答案为:
m i n ( d i s 左 [ u ] + d i s 右 [ v ] + w , d i s 左 [ v ] + d i s 右 [ u ] + w , s u m [ u ] + s u m [ v ] − 2 s u m [ l c a ] ) min(dis左[u]+dis右[v]+w,dis左[v]+dis右[u]+w,sum[u]+sum[v]-2sum[lca]) min(dis[u]+dis[v]+w,dis[v]+dis[u]+w,sum[u]+sum[v]2sum[lca])
最后加起来即可。
需要用到最小生成树、最短路、 l c a lca lca
同时我们必须跑完所有多于边的最短路,记录每个点到其他点的最短路的时候需要离散化一下,不然不是 r e re re就是 m l e mle mle
这是一道简化图的过程,进阶的题目都不再是简单修改算法,而是修改图形去适应算法

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
using namespace std;

int n,m,k;
const int maxn = 100500;
const int maxm = 201000;

struct Edge{
    int from,to;ll dist;
    Edge(){}
    Edge(int _from,int _to,ll _dist):from(_from),to(_to),dist(_dist){}
    friend bool operator < (Edge a,Edge b){return a.dist<b.dist;}};
Edge ed[maxm],edges[maxm];int he[maxn],ne[maxm],etop=1;
void insert(int u,int v,ll w){ed[etop]=Edge(u,v,w);ne[etop]=he[u];he[u]=etop++;}

bool flag[maxm];int change[maxn];
vector<pair<ll,int> >G[maxn];//新的树。
vector<pair<int,int> >last;//剩余的边
vector<ll>value;
int List[maxn];
map<pair<int,int>,int>judge;

int fx[maxn],ans=0;
int find(int x){return fx[x]==x?x:fx[x]=find(fx[x]);}

void kruskal(){
    for(int i=1;i<=n;i++)fx[i]=i;
    for(int i=1;i<etop;i++)edges[i]=ed[i];
    sort(edges+1,edges+etop);
    for(int i=1;i<etop;i++){
        Edge &e = edges[i];
        if(find(e.from)==find(e.to))continue;
        else{
            G[e.from].push_back(make_pair(e.dist,e.to));
            G[e.to].push_back(make_pair(e.dist,e.from));
            judge[make_pair(min(e.from,e.to),max(e.from,e.to))]=1;
            fx[find(e.from)]=find(e.to);
        }
    }
}

ll d[205][maxn];
bool vis[maxn];
priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > q;
void dijkstra(int s){
    memset(vis,0,sizeof(vis));
    int use=change[s];
    //cout<<s<<" "<<use<<endl;
    memset(d[use],0x3f,sizeof(d[use]));
    d[use][s]=0;
    q.push(make_pair(0,s));
    while(!q.empty()){
        int now = q.top().second;
        q.pop();
        if(!vis[now]){
            vis[now] = true;
            for(int i = he[now]; i; i = ne[i]){
                Edge& e = ed[i];
                if(d[use][e.to] > d[use][now] + e.dist){
                    d[use][e.to] = d[use][now] + e.dist;
                    q.push(make_pair(d[use][e.to], e.to));
                }
            }
        }
    }
}

void init(){
    kruskal();
    for(int i=1;i<etop;i++)if(!judge[make_pair(min(ed[i].from,ed[i].to),max(ed[i].from,ed[i].to))]){
        last.push_back(make_pair(ed[i].from,ed[i].to));
        //cout<<ed[i].from<<" "<<ed[i].to<<endl;
        judge[make_pair(min(ed[i].from,ed[i].to),max(ed[i].from,ed[i].to))]=1;
        value.push_back(ed[i].dist);

    }
    for(int i=0;i<last.size();i++){
        change[last[i].first]=(i+1);
        change[last[i].second]=(i+1)+last.size();
        dijkstra(last[i].first);
        dijkstra(last[i].second);
    }
    etop=1;
    memset(he,0,sizeof(he));
    memset(ne,0,sizeof(ne));
    for(int i=1;i<=n;i++)
        for(int j=0;j<G[i].size();j++){
            insert(i,G[i][j].second,G[i][j].first);
            //cout<<i<<" "<<G[i][j].second<<endl;
        }
}

#define K 21
int dep[maxn],root,f[22][maxn];
ll sum[maxn];

void dfs(int u,int fa){
    for(int i=he[u];i;i=ne[i]){
        Edge &e=ed[i];
        if(e.to==fa)continue;
        dep[e.to]=dep[e.from]+1;
        f[0][e.to]=e.from;
        sum[e.to]=sum[e.from]+e.dist;
        dfs(e.to,e.from);
    }
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    int d=dep[x]-dep[y];
    for(int p=0,k=1;p<K;p++,k<<=1){
        if(d&k)x=f[p][x];
    }
    if(x==y)return x;
    for(int i=K-1;i>=0;i--){
        if(f[i][x]==f[i][y])continue;
        x=f[i][x],y=f[i][y];
    }
    return f[0][x];
}

void pre_lca(){
    memset(dep,0,sizeof(dep));
    int root=1;
    dep[root]=1;
    dfs(root,-1);
    for(int j=1;j<K;j++)
        for(int i=1;i<=n;i++)f[j][i]=f[j-1][f[j-1][i]];
}

int main(){
    cin>>n>>m;
    FOR(i,1,m){
        int u,v;ll w;
        scanf("%d%d%lld",&u,&v,&w);
        insert(u,v,w);
        insert(v,u,w);
    }
    init();
    pre_lca();
    ll ans=0;
    cin>>k;List[1]=1;
    FOR(i,1,k)scanf("%d",&List[i+1]);
    for(int i=1;i<=k;i++){
        int u=List[i],v=List[i+1];
        int father=lca(u,v);
        ll tmp=sum[u]+sum[v]-2*sum[father];
        //cout<<u<<" "<<v<<" fa:"<<father<<" "<<tmp<<endl;
        for(int j=0;j<last.size();j++){
            int l=change[last[j].first],r=change[last[j].second];
            //cout<<last[j].first<<" "<<last[j].second<<endl;
            //cout<<d[l][u]<<" "<<d[r][v]<<")("<<d[l][v]<<" "<<d[r][u]<<endl;
            ll tmptmp=min(d[l][u]+d[r][v]+value[j],d[l][v]+d[r][u]+value[j]);
            tmp=min(tmp,tmptmp);
        }
        ans+=tmp;
    }
    cout<<ans<<endl;
}
/*
6 8
1 5 10
5 6 9
5 3 3
6 2 11
2 4 9
4 5 3
3 4 7
1 2 4
1
3
*/

现在我们再来介绍一道题,农夫带狼羊菜过河大家都听说过把。这个题目就是类似的,某个特殊的人可以带别人到对岸,也可以带回来,消耗的体力就是带的人数(包括自己)。同时有 m m m对情侣,必须保证情侣在同一边的时候,这个人一定也在这一边监视。求最少消耗体力使得所有人都到对岸。同时带人的时候,有最大限制 k k k
n 、 m ≤ 15 n、m≤15 nm15
显然易见,状压最短路。
初始状态为 ( 1 &lt; &lt; n + 1 ) − 1 (1&lt;&lt;n+1)-1 (1<<n+1)1,最终状态为 0 0 0,最后一位表示的是特殊的人。
只需要事先暴力枚举剔除不合法状态即可。(遍历所有情侣判断是否对应位为1,特殊的人为0。或者反过来)
对于合法状态,我们需要建立有向边,就是判断合法转移。
考虑对 A A A B B B异或,如果最后一位异或为1,说明特殊的人进行了移动,就是合法条件之一。
异或对应位为1,表示发生了移动,总共移动人数必须小于等于 k k k
并且带到另一边实际上,所以变化的一定都是同样的改变:都是由 0 − &gt; 1 , 1 − &gt; 0 0-&gt;1,1-&gt;0 0>1,1>0
所以我们可以简单地把这样的改变判断等价于 m a x ( A , B ) − m i n ( A , B ) = A ⊕ B max(A,B)-min(A,B)=A\oplus B max(A,B)min(A,B)=AB
如果都能够满足建双向边,跑最短路即可。

#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

#define ll long long
#define eps 1e-6
#define inf 0x3f3f3f3f3f3f3f3f
const int maxn=200050;
const int maxm=400050;

int n,m,k;
pair<int,int>A[50];
vector<int>G;

struct Edge{
    int from,to;ll dist;
    Edge(){}
    Edge(int _from,int _to,ll _dist):from(_from),to(_to),dist(_dist){}
};
Edge ed[maxm];int he[maxn],ne[maxm],etop=1;
void insert(int u,int v,ll w){ed[etop]=Edge(u,v,w);ne[etop]=he[u];he[u]=etop++;}

ll d[maxn];
bool vis[maxn];
priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > q;
void dijkstra(int s){
    memset(vis,0,sizeof(vis));
    memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    q.push(make_pair(0, s));
    while(!q.empty()){
        int now = q.top().second;
        q.pop();
        if(!vis[now]){
            vis[now] = true;
            for(int i = he[now]; i; i = ne[i]){
                Edge& e = ed[i];
                if(d[e.to] > d[now] + e.dist){
                    d[e.to] = d[now] + e.dist;
                    q.push(make_pair(d[e.to], e.to));
                }
            }
        }
    }
}

bool check_1(int u,int x,int y){
    if((u&1)==0){
        //cout<<(u&(1<<x))<<" "<<(u&(1<<y))<<endl;
        if((u&(1<<x))&&(u&(1<<y)))
            return false;
    }
    if((u&1)==1){
        if(((u&(1<<x))==0)&&((u&(1<<y))==0))
            return false;
    }
    return true;
}

bool check_2(int x,int y,int& cnt){
    int tmp=x^y;
    if(tmp&1==1){
        for(int i=0;i<n;i++){
            int x=(1<<i);
            if((tmp&x)>0)cnt++;
        }
        if(cnt<=k){
            if(x<y)swap(x,y);
            if(x-y==tmp)return true;
        }
    }
    return false;
}

int main(){
    cin>>n>>m>>k;n++;
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        A[i]=make_pair(x,y);
    }
    for(int i=0;i<(1<<n);i++){
        int ok = 1;
        for(int j=1;j<=m;j++){
            int x=A[j].first,y=A[j].second;
            if(!check_1(i,x,y)){
                ok=0;
                break;
            }
        }
        if(ok)G.push_back(i);//,cout<<i<<endl;
    }
    for(int i=0;i<G.size();i++)
        for(int j=0;j<G.size();j++){
            int cnt=0;
            if(check_2(G[i],G[j],cnt)){
                //cout<<G[i]<<" "<<G[j]<<":"<<cnt<<endl;
                insert(G[i],G[j],cnt);
            }
        }
    dijkstra(((1<<n)-1));
    if(d[0]==inf)puts("mole");
    else cout<<d[0]<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值