[状压DP]LibreOJ #6177. 「美团 CodeM 初赛 Round B」送外卖2 题解

本文介绍了一种使用状态压缩动态规划(状压DP)解决特定类型路径问题的方法。通过状态压缩来记录每种货物的状态,并针对每个状态寻找最优路径,从而实现最大化任务完成数量的目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(传送门)

题目大意

已知一个有n个节点m条边的有向图,给出k个任务,每个任务从Li开始,到Ri结束,要求在Si接收货物并且在时间结束前Ti送达。(领货和交货不需要消耗时间),问最多能做多少个任务(注意,可以有多个货物在手上)。


解题分析

考虑到这题的k比较小,可以DFS或者是状压DP,DFS没什么好想法,那么开始思考状压DP。

如果求最多能完成几个任务有点烦,可以将货物情况进行状压,由于每个货物可能未送达,已到,又或者还没拿到,所以这道题是3进制存储状态。对于每位i,表示第i个节点的状态:0为未接收,1为已接收未送达,2为已送达。

因为如果送的时间越早,有更多的时间完成后面的任务,所以需要完成当前状态时所耗费的时间越少。但是完成这个状态时在哪?很明显完成状态时所在节点也要枚举。

但是如果走到一个节点然后什么都不会发生,那就是浪费时间。所以其实在枚举状态j之后不是枚举节点i而是任务i,这样只需传到s[i]或t[i],减少转移状态(因为很明显求最短路后直接连起来就行了,无需中间状态)。

上面有点乱,可以直接看转移方程(或者代码),比较清晰:

f[i][j]: f [ i ] [ j ] : 当前状态为j(三进制表示),最后停留在i节点所需的最少时间。

如果当前是要接收这个货物i(对应三进制数位置为1)
f[s[i]][j]=max(L[i],min(f[u][jp[i]]+dis[u][s[i]])) f [ s [ i ] ] [ j ] = m a x ( L [ i ] , m i n ( f [ u ] [ j − p [ i ] ] + d i s [ u ] [ s [ i ] ] ) )
如果当前是要送达这个货物i(对应三进制数位置为2)
f[t[i]]][j]=min(f[u][jp[i]]+dis[u][t[i]]) f [ t [ i ] ] ] [ j ] = m i n ( f [ u ] [ j − p [ i ] ] + d i s [ u ] [ t [ i ] ] )


复杂度

时间: O(nk3k) O ( n ∗ k ∗ 3 k ) ; 空间: O(n3k) O ( n ∗ 3 k ) ;

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxs=59049;
int n,e,k,ans,INF,dis[25][25],a[15],p[15],s[15],t[15],L[15],R[15],f[25][maxs+5];
int po_s(int x,int len){return x/p[len]%3;}
int _count(int x){int tot=0;for (int i=0;i<k;i++) tot+=(po_s(x,i)==2);/* printf("%d %d\n",x,tot); */return tot;}
inline void readi(int &x){
    x=0; char ch=getchar();
    while ('0'>ch||ch>'9') ch=getchar();
    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
void Flyod(){
    for (int i=1;i<=n;i++) dis[i][i]=0;
    for (int t=1;t<=n;t++)
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                if (dis[i][j]>dis[i][t]+dis[t][j]) dis[i][j]=dis[i][t]+dis[t][j];
}
int main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    readi(n); readi(e); readi(k); ans=0;
    memset(dis,63,sizeof(dis));
    for (int i=1,x,y,z;i<=e;i++){
        readi(x); readi(y); readi(z);
        if (z<dis[x][y]) dis[x][y]=z;
    }
    for (int i=0;i<k;i++) {readi(s[i]); readi(t[i]); readi(L[i]); readi(R[i]);}
    Flyod(); p[0]=1; for (int i=1;i<=k;i++) p[i]=p[i-1]*3;
    memset(f,63,sizeof(f)); INF=f[0][0]; f[1][0]=ans=0;
    for (int j=1;j<p[k];j++)
        for (int i=0,q;i<k;i++){
            q=po_s(j,i);
            switch (q){
                case 1:
                    for (int u=1;u<=n;u++) if (f[u][j-p[i]]<INF&&f[u][j-p[i]]+dis[u][s[i]]<=R[i])
                        f[s[i]][j]=max(L[i],min(f[s[i]][j],f[u][j-p[i]]+dis[u][s[i]]));
                break;
                case 2:
                    for (int u=1;u<=n;u++) if (f[u][j-p[i]]<INF&&f[u][j-p[i]]+dis[u][t[i]]<=R[i])
                        f[t[i]][j]=min(f[t[i]][j],f[u][j-p[i]]+dis[u][t[i]]);
                break;
            }
        }
    /*for (int i=1;i<p[k];i++)
        for (int j=1;j<=n;j++) printf("%d %d %d\n",i,j,f[i][j]);*/
    for (int j=1;j<p[k];j++)
        for (int i=1;i<=n;i++)
            if (f[i][j]<INF) ans=max(ans,_count(j));
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值