食材运输

问题描述

在$T$市有很多个酒店,这些酒店对于不同种类的食材有不同的需求情况,莱莱公司负责每天给这些酒店运输食材。

由于酒店众多,如何规划运输路线成为了一个非常重要的问题。你作为莱莱公司的顾问,请帮他们解决这个棘手的问题。

$T$市有$N$个酒店,这些酒店由$N−1$条双向道路连接,所有酒店和道路构成一颗树。

不同的道路可能有不同的长度,运输车通过该道路所需要的时间受道路的长度影响。

在$T$市,一共有$K$种主流食材。

莱莱公司有$K$辆车,每辆车负责一种食材的配送,不存在多辆车配送相同的食材。

由于不同酒店的特点不同,因此不同酒店对食材的需求情况也不同,比如可能 1 号酒店只需要第$1,5$种食材,$2$号酒店需要全部的$K$种食材。

莱莱公司每天给这些公司运输食材。

对于运输第$i$种食材的车辆,这辆车可以从任意酒店出发,然后将食材运输到所有需要第$i$种食材的酒店。

假设运输过程中食材的装卸不花时间,运输车足够大使得其能够在出发时就装满全部所需食材,并且食材的重量不影响运输车的速度。

为了提高配送效率,这$K$辆车可以从不同的酒店出发。

但是由于$T$市对于食品安全特别重视,因此每辆车在配送之前需要进行食品安全检查。

鉴于进行食品安全检查的人手不足,最多可以设置$M$个检查点。

现在莱莱公司需要你制定一个运输方案:选定不超过$M$个酒店设立食品安全检查点,确定每辆运输车从哪个检查点出发,规划每辆运输车的路线。

假设所有的食材运输车在进行了食品安全检查之后同时出发,请制定一个运输方案,使得所有酒店的等待时间的最大值最小。

酒店的等待时间从运输车辆出发时开始计算,到该酒店所有需要的食材都运输完毕截至。

如果一个酒店不需要任何食材,那么它的等待时间为$0$。

输入格式

第一行包含$3$个正整数$N,M,K$,含义见题目描述。

接下来$N$行,每行包含$K$个整数。每行输入描述对应酒店对每种食材的需求情况,$1$表示需要对应的食材,$0$表示不需要。

接下来$N−1$行,每行包含$3$个整数$u,v,w$,表示存在一条通行时间为$w$的双向道路连接$u$号酒店和$v$号酒店。

保证输入数据是一颗树,酒店从$1$编号到$N$。

输出格式

输出一个整数,表示在你的方案中,所有酒店的等待时间的最大值。

数据范围

对于所有数据,满足
$1\leq N\leq 10^2$,
$1\leq M\leq K\leq 10$,
$1\leq u,v\leq N$,
$1\leq w\leq 10^6$。

具体数据规模如下:

输入样例1:

1
2
3
4
5
6
7
8
9
10
11
12
6 1 2
1 0
0 0
1 0
0 1
0 1
0 1
1 2 7
2 3 2
2 4 4
4 5 5
4 6 3

输出样例1:

1
15

样例1解释

样例$1$的输入数据如上图。

由于限制了最多只能设置$1$个检查点,因此可以设置两辆运输车的路径如下:

输入样例2:

1
2
3
4
5
6
7
8
9
10
11
12
6 2 2
1 0
0 0
1 0
0 1
0 1
0 1
1 2 7
2 3 2
2 4 4
4 5 5
4 6 3

输出样例2:

1
9

样例2解释

样例$2$的输入数据和样例$1$几乎完全相同,唯一的区别在于样例$2$中允许最多设置$2$个检查点。

我们可以设置两辆运输车的路径如下:

在$1$号酒店和$6$号酒店设置检查点,最晚拿到所有食材的酒店为$5$号酒店,等待时间为$15$。

解题思路

给定$n$个顶点的树,从中选取不超过$m$个点,在限定时间内实现区间覆盖

  1. 题目要求求解最长时间的最小值,这个值具有二段性:如果时间过小,则无论如何选取$m$个点,都不能实现$k$种食材完全运输完毕;如果时间过大,就有冗余。综上,对时间进行二分。

  2. 暂且不考虑至多选取$m$个点的限制,那么问题可以视为一个区间重复覆盖问题:对于任意一个点$i$,计算其运输$j$种物品的最长时间$d[i][j]$,$state[i]$是一个二进制数,表示在时限从i点出发所能运送完的食材种类状态,能运送完,则相应数位为$1$,否则为$0$;

  3. 根据$2$知,需要$DFS$预处理$d[i][j]$;

  4. 二分的$check$函数:状压$DP$,$f[j]$表示达到状态$j$所需选择的最少节点数量;那么如果f[(1 << k) - 1] <= m,$check$就返回真值即可。

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110,M = 15,inf = 0x3f3f3f3f;

typedef pair<int, int> PII;

#define x first
#define y second

int n,m,k;
int food[N][M]; //存储每个酒店对应的食物,1为需要,0为不要

int d[N][M]; //表示第i个点运送食材j到所有需要的酒店的至少耗时

int f[1 << M]; //j视为二进制数,f[j]表示j的数位为1的食材在时限内运送完所需要的最小检查站数量

int state[N]; //state存储二进制数,state[i]的数位1表示能在时限内从i酒店出发并运送完毕的食材种类

int h[N],idx;
struct edge{
int to,ne,w;
}e[N * 2];

void init(){
for(int i = 1; i <= n; ++ i) h[i] = -1;
}

void add(int x,int y,int w){
e[idx].to = y,e[idx].w = w,e[idx].ne = h[x],h[x] = idx ++;
}

//dfs计算d[u][k]
PII dfs(int u,int fa,int k) {
PII res = {0,-1}; //first表示所有需要运送的酒店的边权的二倍,second表示其中最长的一条路
if(food[u][k]) res.y = 0;
for(int i = h[u]; ~i; i = e[i].ne) {
int j = e[i].to,w = e[i].w;
if(j == fa) continue;
PII t = dfs(j,u,k);
if(t.y != -1) { //表示子树中存在需要运送的酒店
res.x += t.x + w * 2;
res.y = max(res.y,w + t.y);
}
}
return res;
}

bool check(int mid) {
for(int i = 1; i <= n; ++ i) state[i] = 0;
for(int i = 1; i <= n; ++ i)
for(int j = 0; j < k; ++ j)
if(d[i][j] <= mid)
state[i] |= 1 << j;
for(int i = 0; i < 1 << k; ++ i) f[i] = inf;
f[0] = 0;
for(int i = 0; i < 1 << k; ++ i)
for(int j = 1; j <= n; ++ j)
f[i | state[j]] = min(f[i | state[j]],f[i] + 1);
return f[(1 << k) - 1] <= m;
}

int main() {
cin.tie(nullptr);cout.tie(nullptr);ios::sync_with_stdio(false);
cin >> n >> m >> k;
for(int i = 1; i <= n; ++ i)
for(int j = 0; j < k; ++ j)
cin >> food[i][j];
init();
int a,b,w;
for(int i = 1; i <= n - 1;++ i) {
cin >> a >> b >> w;
add(a,b,w);add(b,a,w);
}
for(int i = 1; i <= n; ++ i)
for(int j = 0; j < k; ++ j) {
PII t = dfs(i,-1,j);
if(~t.y) d[i][j] = t.x - t.y;
}
int l = 0,r = inf;
while(l < r) {
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << r << endl;
return 0;
}