CSP202012-4 食材运输

原题点我

又是树,又让我想起CSP-S 2019……

先从部分分入手。

链状:对于每一种食材,先枚举一个出发点,计算每个点到出发点之间的距离。从特殊情况入手,只有出发点的一侧有要到达的酒店,这时,总距离就是出发点到最远的酒店的距离;对于一般的情况,出发点的左右两侧都有要到达的酒店,不妨设最右边的酒店离出发点最远,则总距离就是(出发点到最左边的点的距离 * 2 + 出发点到最右边的点的距离)。对这两种情况归纳一下,答案就是(最左边的点到最右边的点的距离 * 2 - 离出发点最远的点到出发点的距离)。
树状:把链状的结论迁移到树上,我们可以得出这样的结论:把出发点和各目标点之间的路径涂黑,最终答案就是(被涂黑的路径的总长度 * 2 - 出发点到最远点的路径长度)。
至于为什么是减去最远点的距离:如果从出发点出发,经过所有目标点之后再返回出发点的话,总路径长度是固定的,总是被涂黑的路径总长度的2倍。但现在不要求返回,所以可以省掉某一个点回到出发点的距离,而这个距离最大时,总距离最小。
M == K : 每一种食材都可以单独地建立一个出发点而互不影响,直接计算即可。
M < K : 可建立的出发点比食材数少,会出现一个出发点要运行两辆车的情况。而题中出现“最大值最小”,显然是二分。K<=10,肯定是要对食材种类进行状态压缩。结合上述条件,我们得出以下算法:
1.计算出每个点运输每种食材所需的最小时间. timi,j表示第i个点运输第j种食材所需的最小时间
2.二分答案,即二分在mid时间内,建立不多于m个出发点是否能满足题意
3.判断mid是否满足题意。
关键就在于check函数的实现上。
对于每一个出发点来说,我们建立一个数组ok[maxn]oki表示出发点i在mid时间内能解决的食材种类的状态压缩(一个10位二进制数)。对于每一个点来说,可以在这个点建立或不建立出发点。设fi,j,p表示在前i个点中建立了j个出发点,要满足状态为p是否可行,则有f[i+1,j+1,p|oki+1]|=f[i,j,p]f[i+1,j,p]|=f[i,j,p]

初始状态:f[0,0,0]=1

最终状态:f[n,m,(1<<k)1]=?

#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);
    
}

总结

从部分分入手,找到解题思路。运用二分和状态压缩的题目中的提示,找到满分解法。

posted @   Franky0705  阅读(228)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示