【loj6177】「美团 CodeM 初赛 Round B」送外卖2 Floyd+状压dp

题目描述

一张$n$个点$m$条边的有向图,通过每条边需要消耗时间,初始为$0$时刻,可以在某个点停留。有$q$个任务,每个任务要求在$l_i$或以后时刻到$s_i$接受任务,并在$r_i$或以前时刻到$t_i$完成任务。同一时刻可以接受多个任务。问:最多能完成多少任务。

输入

第一行,三个正整数$n$、$m$、$q$;

接下来$m$行,每行三个正整数$u_i$、$v_i$、$c_i$,表示有一条从$u_i$到$v_i$,耗时$c_i$的边。

接下来$q$行,每行四个正整数$s_i$、$t_i$、$l_i$、$r_i$,描述一个任务。

输出

一个整数,表示最多能完成的任务数量。

样例输入

5 4 3
1 2 1
2 3 1
3 4 1
4 5 1
1 2 3 4
2 3 1 2
3 4 3 4

样例输出

2


题解

Floyd+状压dp

比赛时现场切的题,今天想到,于是写了题解。

显然两点之间一定是走最短路的,于是先使用Floyd预处理出两点之间的最短路。

然后考虑状压。由于一个任务有三种状态:未接受、接受但未完成、已完成。于是可以设三进制状态,表示每个任务的进程。

而时间范围过大无法设为状态,于是需要用dp值表示。考虑到可以逗留,因此时间越短,答案不会更劣。

于是设$f[i][j]$表示任务进程的状态为$i$,当前在点$j$的最短时间。那么枚举每一个任务,考虑其转移方式即可。

注意这里只需要考虑第二个点是任务的起点/终点的情况,因为保证了走的是最短路,中间的点停留没有意义(所以本题n和m可以出到3W左右)。

最后判断哪个状态合法,统计该状态能够做的任务个数即可。

时间复杂度$O(3^q·q^2)$。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int dis[25][25] , f[60000][25] , base[15] , qx[15] , qy[15] , ql[15] , qr[15];
int main()
{
	int n , m , q , i , j , k , x , y , z , ans = 0 , tmp;
	scanf("%d%d%d" , &n , &m , &q);
	memset(dis , 0x3f , sizeof(dis));
	for(i = 1 ; i <= n ; i ++ ) dis[i][i] = 0;
	for(i = 1 ; i <= m ; i ++ ) scanf("%d%d%d" , &x , &y , &z) , dis[x][y] = min(dis[x][y] , z);
	for(k = 1 ; k <= n ; k ++ )
		for(i = 1 ; i <= n ; i ++ )
			for(j = 1 ; j <= n ; j ++ )
				dis[i][j] = min(dis[i][j] , dis[i][k] + dis[k][j]);
	base[0] = 1;
	for(i = 1 ; i <= q ; i ++ ) scanf("%d%d%d%d" , &qx[i] , &qy[i] , &ql[i] , &qr[i]) , base[i] = base[i - 1] * 3;
	memset(f , 0x3f , sizeof(f)) , f[0][1] = 0;
	for(i = 1 ; i < base[q] ; i ++ )
	{
		for(j = 1 ; j <= n ; j ++ )
		{
			for(k = 1 ; k <= q ; k ++ )
			{
				if(i % base[k] / base[k - 1] == 1)
					f[i][qx[k]] = min(f[i][qx[k]] , max(f[i - base[k - 1]][j] + dis[j][qx[k]] , ql[k]));
				else if(i % base[k] / base[k - 1] == 2 && f[i - base[k - 1]][j] + dis[j][qy[k]] <= qr[k])
					f[i][qy[k]] = min(f[i][qy[k]] , f[i - base[k - 1]][j] + dis[j][qy[k]]);
			}
		}
	}
	for(i = 0 ; i < base[q] ; i ++ )
	{
		for(j = 1 ; j <= n ; j ++ )
		{
			if(f[i][j] != 0x3f3f3f3f)
			{
				for(tmp = k = 0 ; k < q ; k ++ )
					if(i % base[k + 1] / base[k] == 2)
						tmp ++ ;
				ans = max(ans , tmp);
			}
		}
	}
	printf("%d
" , ans);
	return 0;
}
原文地址:https://www.cnblogs.com/GXZlegend/p/7657769.html