[CSP-S 2022] 假期计划 题解

题面

题面冗长,不便展示,详见 洛谷

考场想法

对于每一个点给他能到达的点都建一条边,最后跑一遍 DFS。期望分数:60

代码

朴素想法

枚举所有可能的四个点,看是否能 “互通有无”,如果可以就更新最终答案。

时间复杂度:O(n4)

期望分数:55

正解思路

优化朴素想法,其实我们可以进行贪心考虑。

预处理

说在前面,可以预处理出来一个点是否能够到达另一个点,由于边权都是 1,因此用 BFS 可实现,即从每个点开始遍历,能遍历到的点都记为可到达,时间复杂度:O(nm),可接受。

贪心

不妨设当前的路径是:1abcd1

发现在考虑1abc?1 的时候,可以贪心地找到 c1 之间可行的点权最大的点,将其作为 d

对应地,在考虑 1?bcd1 的时候,也可以这么贪心出 a

于是想到,有没有可能只需要枚举 bc 就可以了呢?答案是肯定的!(只不过需要判断一下 bc 是否可达)

结合之前的想法,整个问题就基本解决了,让我们捋一下思路:

  1. 二重循环确定 b,c
  2. 找到 1bc1 之间的可行点权最大者,将其分别置为 a,d
  3. 输出答案,完美撒花~

噢噢,看起来这样就可以了呢,让我们来实现一下吧——

可以了吗?(贪心中的纰漏)

注意,实际操作的时候 a,b,c,d 这四个点是有可能重复的,这样就不行了。

哦我的老伙计,那我们该怎么办呢!

答案是:给 a,d 两个点存三个可能的备胎,这样即使发生:

ac 重合了,换完了又与 d 重合了

的情况也不怕,换上第三个备胎就好了,对于 d 同理。

如果这样实现有些许麻烦,可以这么做:

枚举所有可能的备胎组合情况,取四个点都不重的情况中的点权和最大值。

这样省去了许多判断,更简洁优美。

形式化地说:预处理出这,么一个数组:choice[i][j](j{0,1,2}),表示点 i 和点 1 同时可达的点中,点权第 j+1 大的点。

需要注意,可能点 i 和点 1 同时可达的点不足三个,这时候 j 取不到 2,可能产生越界,有很多方式避免他,这里我选择用 vector 类型存储 choice[][]

结合之前的想法,整个问题就正式解决了,让我们捋一下思路:

  1. 二重循环确定 b,c
  2. 枚举所有可能的 choice[b][]choice[c][],作为 a,d,判断四个点 a,b,c,d 是否重合,如果不重合就统计他的点权和并与当前答案比较;
  3. 输出答案,完美撒花~

参考代码

// Problem: P8817 [CSP-S 2022] 假期计划
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P8817
// Memory Limit: 512 MB
// Time Limit: 2000 ms
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2022-12-18 17:33:37

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#define INF 0x3f3f3f3f
#define int long long
using namespace std;

const int N = 2510;

int score[N]; // 点权
bool pass[N][N]; // 记录两点是否可达
int n, m, k;

vector<int> g[N]; // 图
vector<int> choice[N]; // 备胎们

void init()
{
    cin >> n >> m >> k;
    for (int i = 2; i <= n; i++)
        cin >> score[i];
    for (int i = 1; i <= m; i++)
    {
        int a, b;
        cin >> a >> b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
}

void bfs(int s)
{
    int dist[N];
    memset(dist, 0x3f, sizeof dist);
    dist[s] = 0;
    queue<int> q;
    q.push(s);
    while (q.size())
    {
        auto t = q.front();
        q.pop();

        if (s != t)
        {
            pass[s][t] = 1;
            if (s != 1 && pass[1][t]) 
            {
                choice[s].push_back(t);
                sort(choice[s].begin(), choice[s].end(), 
                [](int a, int b)
                { 
                    return score[a] > score[b];
                });

                if (choice[s].size() > 3) // 不可超了
                    choice[s].pop_back();
            }
        }

        if (dist[t] == k + 1) // 超出可达的范围
            continue;

        for (auto i : g[t])
            if (dist[i] > dist[t] + 1)
                dist[i] = dist[t] + 1, q.push(i);
    }
}

int mx = -INF;

bool diff(int a, int b, int c, int d)
{
    return a != c && b != d && a != d;
}

signed main()
{
    init();
    for (int i = 1; i <= n; i++)
        bfs(i);

    for (int b = 2; b <= n; b++)
        for (int c = 2; c <= n; c++)
            if (pass[b][c] && b != c)
                for (auto i : choice[b])
                    for (auto j : choice[c])
                        if (diff(i, b, c, j))
                            mx = max(mx, score[b] + score[c]
                                     + score[i] + score[j]);

    cout << mx << endl;
    return 0;
}

posted @   MoyouSayuki  阅读(412)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
:name :name
点击右上角即可分享
微信分享提示