「美团 CodeM 初赛 Round B」送外卖2

题目描述:

一张n个点m条有向边的图上,有q个配送需求,需求的描述形式为(si,ti,li,ri),即需要从点si送到ti,在时刻li之后(包括li)可以在si领取货物,需要在时刻ri之前(包括ri)送达ti,每个任务只需完成一次。图上的每一条边均有边权,权值代表通过这条边消耗的时间。在时刻0有一个工作人员在点1上,求他最多能完成多少个配送任务。在整个过程中,可以认为领货跟交货都是不消耗时间的,时间只花费在路程上。当然在一个点逗留也是允许的。

数据范围:

2n20

1m400

1q10

--------------------------------------------------我是华丽的分割线--------------------------------------------------

题解:

这是一道状态压缩DP大水题。

需要注意的是,给出的边都是有向边,且允许外卖小哥同时携带多个货物。

观察数据范围,发现数据特别小,考虑暴力枚举配送顺序全排列然后模拟,但是显然TLE...

于是乎......

考虑到每个任务都只有3种可能状态(未取货、已取货但未送达和已送达),可以写一个3进制的状压DP。但是由于3进制用起来有那么一点麻烦,且空间复杂度可以承受,便考虑使用2个二进制数来表示状态。于是乎就定义F[i][j][k]用来表示是当前状态所需的最少时间,i表示当前在所在节点的编号,j和k为二进制数,分别表示当前身上所携带的货物和未送达的货物。

接下来考虑状态转移。

对于当前的F[i][j][k],枚举下一个未完成的任务ta。如果该任务已经携带在身上了,那么判断一下从当前状态转移是否更优(当然大前提是现在立马敢去来得及),更优的话更新下一状态。如果该任务还没有携带到身上,那么就去取货,同样也需要判断一下从当前状态转移是否更优。

接下来就可以开始进行愉快的DFS了。

考虑当初DP的定义,不难发现答案就是所有可能状态(就是被刷到过的状态)中已完成任务数量的最大值。

最后再贴上C++代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,q,ans,F[21][1025][1025],dist[21][21],s[11],t[11],l[11],r[11];
void Update(int k)
{
	int tot=0;
	for(int ta=1;ta<=q;ta++)
		if(!(k&(1<<ta-1)))
			tot++;
	if(tot>ans) ans=tot;
}
void DFS(int i,int j,int k)
{
	Update(k);//更新答案
	for(int ta=1;ta<=q;ta++)
	{
		if(k&(1<<ta-1))//第ta个任务未派送
		{
			if(j&(1<<ta-1))//身上已经带了第ta个任务,把任务送达
			{
				if(F[i][j][k]+dist[i][t[ta]]<F[t[ta]][j-(1<<ta-1)][k-(1<<ta-1)]&&F[i][j][k]+dist[i][t[ta]]<=r[ta])
				{
					F[t[ta]][j-(1<<ta-1)][k-(1<<ta-1)]=F[i][j][k]+dist[i][t[ta]];
					DFS(t[ta],j-(1<<ta-1),k-(1<<ta-1));
				}
			}
			else//身上没有第ta个任务,去把任务取来
			{
				if(max(F[i][j][k]+dist[i][s[ta]],l[ta])<F[s[ta]][j+(1<<ta-1)][k])
				{
					F[s[ta]][j+(1<<ta-1)][k]=max(F[i][j][k]+dist[i][s[ta]],l[ta]);
					DFS(s[ta],j+(1<<ta-1),k);
				}
			}
		}
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	memset(dist,63,sizeof(dist));memset(F,63,sizeof(F));
	for(int i=1;i<=m;i++)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		if(dist[a][b]>c) dist[a][b]=c;
	}
	for(int i=1;i<=q;i++)
		scanf("%d%d%d%d",&s[i],&t[i],&l[i],&r[i]);
	for(int k=1;k<=n;k++)                            //Floyd刷出每两点间的最短路
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				if(dist[i][k]+dist[k][j]<dist[i][j])
					dist[i][j]=dist[i][k]+dist[k][j];
	for(int i=1;i<=n;i++) dist[i][i]=0;
	F[1][0][(1<<q)-1]=0;
	DFS(1,0,(1<<q)-1);
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/XuZhiyuan_XZY/article/details/80029399
今日推荐