简谈分层图

分层图是一个处理对边存在额外操作的较好的处理方式。
这里引入一道例题,在正常的最短路中,你可以挑选k条路不需要花费时间,那么最小总花费是多少
存在一种 d p dp dp的处理方式, d p [ i ] [ j ] dp[i][j] dp[i][j]表示到第i个节点用了j次免费, d p [ n ] [ 0   k ] dp[n][0~k] dp[n][0 k]即为答案。
那么怎么去转移呢, d p [ v ] [ j ] = m i n ( d p [ u ] [ j − 1 ] , d p [ u ] [ j ] + u − > v ) dp[v][j]=min(dp[u][j-1],dp[u][j]+u->v) dp[v][j]=min(dp[u][j1],dp[u][j]+u>v)
我们可以把其转换成k+1个图,分别代表用了几次免费机会的图,并且免费是不断用的次数增加的,也就是说 i i i个图和第 j j j个图有单向边相连,边权为0,代表用了一次免费机会
如何去添加这些边呢,就是将 i − > j i->j i>j在每张图都加上,同时 i − > j + n i->j+n i>j+n也加上(同样在每张图)。
这里我用 ( ( k − 1 ) n + 1 − > k n ) ((k-1)n+1 -> kn) ((k1)n+1>kn)表示第 k k k层的节点。
简要模板如下:

int n,m,k;
const int maxn = 10000000;
const int maxm = 20000000;
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++;}

void add_edge(int u,int v,ll w){
    for(int i=u,j=v;i<=u+k*n;i+=n,j+=n)insert(i,j,w);
    for(int i=u,j=v+n;j<=v+k*n;i+=n,j+=n)insert(i,j,0);
}

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));
                }
            }
        }
    }
}

代码很简单,就是加上了边,所以分层图只是一种思想,没必要死记硬背。
但是这样的代码会 m l e mle mle,主要在于大量使用了一些不必要的点和边。
其实每次我们只需要对相邻层进行转移即可,其他都可以映射到初始图进行。
d [ i ] [ p ] d[i][p] d[i][p]表示到第 i i i个点,使用了 p p p次免费机会。
每次松弛的时候,对同一层松弛,同时判断是否还有免费机会,如果有,对下一层也松弛。

int n,m,k;
const int maxn = 100000;
const int maxm = 400010;
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][30];
bool vis[maxn][30];
struct node{
    int to,level;
    ll cost;
    friend bool operator < (node a,node b){
        return a.cost>b.cost;
    }
};
priority_queue<node>q;

void dijkstra(int s){
    memset(vis,0,sizeof(vis));
    memset(d,0x3f,sizeof(d));
    d[s][0]=0;
    q.push(node{s,0,0});
    while(!q.empty()){

        int now = q.top().to,p=q.top().level;        //cout<<d[now][p]<<" "<<q.size()<<endl;
        q.pop();
        if(!vis[now][p]){
            vis[now][p] = true;
            for(int i = he[now]; i; i = ne[i]){
                Edge& e = ed[i];
                if(!vis[e.to][p]&&d[e.to][p]>d[now][p]+e.dist){
                    d[e.to][p]=d[now][p]+e.dist;
                    q.push(node{e.to,p,d[e.to][p]});
                }
                if(p<k&&!vis[e.to][p+1]&&d[e.to][p+1]>d[now][p]){
                    d[e.to][p+1]=d[now][p];
                    q.push(node{e.to,p+1,d[e.to][p+1]});
                }
            }
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值