CSP202012-4 食材运输
又是树,又让我想起CSP-S 2019……
先从部分分入手。
链状:对于每一种食材,先枚举一个出发点,计算每个点到出发点之间的距离。从特殊情况入手,只有出发点的一侧有要到达的酒店,这时,总距离就是出发点到最远的酒店的距离;对于一般的情况,出发点的左右两侧都有要到达的酒店,不妨设最右边的酒店离出发点最远,则总距离就是(出发点到最左边的点的距离 * 2 + 出发点到最右边的点的距离)。对这两种情况归纳一下,答案就是(最左边的点到最右边的点的距离 * 2 - 离出发点最远的点到出发点的距离)。
树状:把链状的结论迁移到树上,我们可以得出这样的结论:把出发点和各目标点之间的路径涂黑,最终答案就是(被涂黑的路径的总长度 * 2 - 出发点到最远点的路径长度)。
至于为什么是减去最远点的距离:如果从出发点出发,经过所有目标点之后再返回出发点的话,总路径长度是固定的,总是被涂黑的路径总长度的2倍。但现在不要求返回,所以可以省掉某一个点回到出发点的距离,而这个距离最大时,总距离最小。
M == K : 每一种食材都可以单独地建立一个出发点而互不影响,直接计算即可。
M < K : 可建立的出发点比食材数少,会出现一个出发点要运行两辆车的情况。而题中出现“最大值最小”,显然是二分。K<=10,肯定是要对食材种类进行状态压缩。结合上述条件,我们得出以下算法:
1.计算出每个点运输每种食材所需的最小时间. 表示第个点运输第种食材所需的最小时间
2.二分答案,即二分在mid时间内,建立不多于m个出发点是否能满足题意
3.判断mid是否满足题意。
关键就在于check函数的实现上。
对于每一个出发点来说,我们建立一个数组,表示出发点在mid时间内能解决的食材种类的状态压缩(一个10位二进制数)。对于每一个点来说,可以在这个点建立或不建立出发点。设表示在前个点中建立了个出发点,要满足状态为是否可行,则有和
初始状态:
最终状态:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
const int maxn = 110;
const int maxm = 15;
int n, m, k;
int need[maxn][maxm];
int d[maxn];
bool vis[maxn];
int head[maxn], ecnt;
int tim[maxn][maxm];
vector<int> tobe[maxm];
struct EDGE
{
int v, w, nxt;
}edge[maxn * 2];
inline void add(int a, int b, int c)
{
edge[++ecnt].v = b;
edge[ecnt].w = c;
edge[ecnt].nxt = head[a];
head[a] = ecnt;
}
bool dfs(int x, int fa, int &ans, int food)
{
bool flag = false;
if(need[x][food])
flag = true;
for(int i = head[x]; i; i = edge[i].nxt)
{
int v = edge[i].v;
if(v == fa)
continue;
d[v] = d[x] + edge[i].w;
bool t = dfs(v, x, ans, food);
if(t)
{
ans += 2 * edge[i].w;
flag = true;
}
}
return flag;
}
const int maxst = 1 << 11 ;
int ok[maxn];
int f[maxn][maxn][maxst];
bool check(int x)
{
memset(ok, 0, sizeof(ok));
memset(f, 0, sizeof(f));
f[0][0][0] = 1;
for(int i = 1; i <= n; i++)
for(int j = 0; j < k; j++)
if(tim[i][j + 1] <= x)
ok[i] |= 1 << j;
for(int i = 0; i <= n; i++)
for(int j = 0; j <= m; j++)
for(int p = 0; p < (1 << k); p++)
{
f[i + 1][j + 1][p | ok[i + 1]] |= f[i][j][p];
f[i + 1][j][p] |= f[i][j][p];
}
return f[n][m][(1<<k) - 1];
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= k; j++)
scanf("%d", &need[i][j]);
for(int i = 1; i < n; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
add(v, u, w);
}
for(int i = 1; i <= k; i++)
{
for(int j = 1; j <= n; j++)
{
d[j] = 0;
int tt = 0;
dfs(j, 0, tt,i);
int maxdis = 0;
for(int p = 1; p <= n; p++)
if(need[p][i])
maxdis = max(maxdis, d[p]);
tt -= maxdis;
tim[j][i] = tt;
}
}
int l = 0, r = 1e8;
while(l < r)
{
int mid = (l + r) >> 1;
if(check(mid))
r = mid;
else
l = mid + 1;
}
printf("%d\n", l);
}
总结
从部分分入手,找到解题思路。运用二分和状态压缩的题目中的提示,找到满分解法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现