题目描述
一张$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; }