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