送外卖 拓扑排序+状压DP+最短路

本文介绍了一道算法题的解决思路,通过状压DP和拓扑排序的方法来求解带有限制条件的最短路径问题。文章详细阐述了如何利用状态压缩技术将问题转化为适合动态规划的形式,并结合拓扑排序确保了送餐顺序符合公司的规定。

题面


1.注意到K=20,这样的数据范围让人想到状压DP,而且允许进行K次Dijkstra算法。

2.题目中“公司规定了其中某些小区送餐的先后顺序,比如i小区的餐必须在给j小区送餐前送到”释放出明显的拓扑排序信号。

3.设 f[i][j] 表示在i表示的状态下终点为j时的最小路程和,容易写出状态转移方程:

f[en][j]=min{f[st][k]+dis[k][j]} ,其中dis[k][j]表示k到j的最短距离,这个可以用最短路算法处理。满足拓扑序列关系时才可以转移

#include<stdio.h>
#include<stack>
#include<queue>
#define ll long long
#define MAXN 20005
#define MAXM 400005
#define Min(x,y) ((x<y)?(x):(y))
using namespace std;

int N,M,K,T,ok[25],Lim;
ll f[1050000][25],Ans=2e9;

inline int _R()
{
    char s=getchar();int v=0,sign=0;
    while((s!='-')&&(s>57||s<48))s=getchar();
    if(s=='-')sign=1,s=getchar();
    for(;s>47&&s<58;s=getchar())v=v*10+s-48;
    if(sign)v=-v;
    return v;
}

int en[MAXM],nex[MAXM],las[MAXN],len[MAXM],tot;
void ADD(int x,int y,int z)
{
    en[++tot]=y;
    nex[tot]=las[x];
    las[x]=tot;
    len[tot]=z;
}

int En[MAXM],Nex[MAXM],Las[MAXN],Tot;
void add(int x,int y)
{
    En[++Tot]=y;
    Nex[Tot]=Las[x];
    Las[x]=Tot;
}

ll dis[25][MAXN];
void Dijkstra(int s,int p)
{
    int i,x,y;
    ll z;
    priority_queue<pair<ll,int> >Q;

    for(i=1;i<=N;i++)dis[p][i]=2e9;
    dis[p][s]=0;
    Q.push(make_pair(0,s));
    while(Q.size())
    {
        x=Q.top().second;
        z=Q.top().first;
        Q.pop();
        if(dis[p][x]!=-z)continue;
        for(i=las[x];i;i=nex[i])
        {
            y=en[i];
            if(dis[p][y]>dis[p][x]+len[i])
            {
                dis[p][y]=dis[p][x]+len[i];
                Q.push(make_pair(-dis[p][y],y));
            }
        }
    }
}

int D[400];
stack<int>S;
int main()
{
    int i,j,k,x,y,z,cnt=0,Size;

    N=_R();M=_R();K=_R();
    for(i=1;i<=M;i++)
    {
        x=_R();y=_R();z=_R();
        ADD(x,y,z);
        ADD(y,x,z);
    }
    T=_R();
    for(i=1;i<=T;i++)
    {
        x=_R();y=_R();
        add(x,y);
        D[y]++;
    }

    for(i=1;i<=K+1;i++)Dijkstra(i,i);

    for(i=2;i<=K+1;i++)if(!D[i])S.push(i);
    while(S.size())
    {
        x=S.top();S.pop();
        for(i=Las[x];i;i=Nex[i])
        {
            y=En[i];
            D[y]--;
            ok[y]=ok[y]|ok[x]|(1<<x-2);
            if(!D[y])S.push(y);
        }
    }
    //ok[i]表示i要加入时的前提条件
    Lim=(1<<K)-1;

    for(i=0;i<=Lim;i++)
    for(j=0;j<=K+1;j++)f[i][j]=2e9;
    f[0][1]=0;
    for(i=0;i<=Lim;i++)
    {
        for(j=1;j<=K+1;j++)
        {
            if(f[i][j]==2e9)continue;
            for(k=2;k<=K+1;k++)
            {
                if(((i&ok[j])!=ok[j]))continue;
                f[i|(1<<k-2)][k]=Min((f[i|(1<<k-2)][k]),(f[i][j]+dis[j][k]));
            }
        }
    }

    for(i=1;i<=K+1;i++)Ans=Min(Ans,(f[Lim][i]+dis[i][N]));
    printf("%lld",Ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值