20个点的数据,基本上可以判断是状态压缩,所以就直接开始上手了。
基本思路还是很好想,构建出图之后用floyd构建全图联通,再用状压dp求解答案。
而难点在于,定义dp数组的含义。
我们回忆一下状压dp求解哈密顿路径的例题:
dp数组的两个维度表示的是当前状态和落脚点,值表示最短路径
当前状态包含了所有的已遍历点,落脚点是题目的限定条件(结束时要在n-1落脚)
那我们再看这道题,
显然这道题并不需要遍历所有点,而是要尝试遍历所有的订单
所以我们肯定将订单化为二进制表示的状态……emmm真的是二进制吗?
当然不是,每个订单有未接单,已接单和已送达三种状态
所以我们要用三进制表示这个数
那另外一维和值怎么处理呢?
我们在判定订单能否按时接单的时候,首先要看外卖小哥和商家的距离,再要看商家和客户的距离
所以我们需要的是:起点和距离
这样就比较好分析了:
我们定义f[i][j]为从i点出发,到达j状态需要花费的时间 当j状态对应的3进制位值为0时,则该状态未接单,为1时该状态已经接单,为2时该状态已送达
正解代码如下
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=22,M=70000;
long long f[N][M];
long long mp[N][N];
int n,m,q;
struct _{
int s,t,l,r;
}task[22];
int ans=0;
long long tbit[12];//三进制数组,最大范围是10个三进制数,和最大订单数
int main(){
tbit[0]=1;
for(int i=1;i<12;i++){//构建三进制
tbit[i]=tbit[i-1]*3;
}
cin>>n>>m>>q;
memset(mp,0x3f,sizeof mp);
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
mp[x][y]=min(mp[x][y],(long long)z);
}
for(int i=0;i<q;i++){
int a,b,c,d;
cin>>a>>b>>c>>d;
task[i]={a,b,c,d};
}
for(int i=0;i<=n;i++) mp[i][i]=0;
for(int l=1;l<=n;l++) //floyd求最短全连通
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
mp[i][j]=min(mp[i][j],mp[i][l]+mp[l][j]);
memset(f,0x3f,sizeof f);
f[1][0]=0;
for(int st=0;st<tbit[q];st++){//遍历所有状态
for(int i=1;i<=n;i++){
if(f[i][st]>=0x3f3f3f3f) continue;
int sum=0;//记录该状态下的最大送达单数
for(int j=0;j<q;j++){//遍历所有订单
if((st/tbit[j])%3==0){//如果当前该订单未接单
//出发接单
f[task[j].s][st+tbit[j]]=min(f[task[j].s][st+tbit[j]],max(f[i][st]+mp[i][task[j].s],(long long)task[j].l));
}
else if((st/tbit[j])%3==1){//如果当前该订单已接单
//出发送单
if(f[i][st]+mp[i][task[j].t]<=task[j].r)
f[task[j].t][st+tbit[j]]=min(f[task[j].t][st+tbit[j]],f[i][st]+mp[i][task[j].t]);
}
else sum++;//如果当前订单已送达
}
ans=max(ans,sum);//更新状态
}
}
cout<<ans;
}
京公网安备 11010502036488号