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

题面

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

考场想法

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

代码

朴素想法

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

时间复杂度:\(O(n^4)\)

期望分数:\(55\)

正解思路

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

预处理

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

贪心

不妨设当前的路径是:\(1\rightarrow a\rightarrow b\rightarrow c\rightarrow d\rightarrow 1\)

发现在考虑\(1\rightarrow a\rightarrow b\rightarrow c\rightarrow ?\rightarrow 1\) 的时候,可以贪心地找到 \(c\rightarrow 1\) 之间可行的点权最大的点,将其作为 \(d\)

对应地,在考虑 \(1\rightarrow ?\rightarrow b\rightarrow c\rightarrow d\rightarrow 1\) 的时候,也可以这么贪心出 \(a\)

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

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

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

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

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

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

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

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

\(a\)\(c\) 重合了,换完了又与 \(d\) 重合了

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

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

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

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

形式化地说:预处理出这,么一个数组:\(choice[i][j](j\in\{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 @ 2022-12-18 19:02  MoyouSayuki  阅读(399)  评论(0编辑  收藏  举报
:name :name