CF1515F Phoenix and Earthquake 题解

题目链接:CF 或者 洛谷

首先基于一个事实,答案一定是生成树,显然,每次我们都需要连边,每次都会 \(-x\),那么一共会减少 \((n-1)\times x\),很显然的一个必要条件为:

\[\sum_{i=1}^{n}a_i \ge (n-1) \times x \ 显然一定成立。 \]

现在我们用来证明它同时也是一个充分条件,不妨设:

\[a_1 \le a_2 \le a_3\le a_4.... \le a_n,且\ \sum_{i=1}^{n}a_i \ge (n-1) \times x。 \]

假如无法找到 \(a_i+a_j \ge x\),不妨考虑极端情况,考虑与 \(a_n\) 相连的点 \(a_i\),假如有 \(a_i+a_n < x\),显然有 \(a_n<x-a_i\),所以此时有:

\[a_1 \le a_2 \le a_3...a_n < x-a_i,\sum_{i=1}^{n}a_i <(n-1)\times (x-a_i)+a_i \]

\[\Rightarrow 原式<(n-1)\times x-(n-2)\times a_i,又知道\ n \ge 2,显然与条件矛盾。 \]

反证出我们一定能找到一组 \(a_i+a_j \ge x\)。我们将它取出,点数 \(-1\),总权减少了 \(x\)

\[\sum_{i=1}^{n-1}a_i \ge (n-2) \times x,显然又是一个同样的子问题。 \]

\(i=2\) 时,显然成立,所以这不仅是一个必要条件,同时还是个充分条件。

考虑构造的强结论

基于上述我们知道的充要条件,即满足那个式子以后,我们每次取出一对满足题意的点对,一定能构造出答案,现在我们来考虑不满足的情况,显然全部都 \(a_i \ge x\) 一定可以随意构造出答案,我们考虑不满足时有:

\[a_1 \le a_2 \le a_3....<x \le a_i \le a_{i+1}...\le a_n \]

这个式子一定是成立的,否则找不到当前一对不满足题意的点,我们可以画出它的连边可能性图:

很显然,有三类可能性边:

  1. \(a_i,a_j \ge x\),这类显然满足题意。

  2. $a_i \ge x,a_j <x $,这类也显然满足题意。

  3. \(a_i,a_j <x\),这类可能和 \(\ge x\),也可能 \(<x\)

我们其实只需要考虑 \(<x\) 的这种该如何通过其他边激活成功。我们考虑其他三类边对它的影响,很显而易见需要考虑的是如图中的 \(1-2\) 这条边。而激活一条边,把另一个点的点权减去 \(x\)\(a_i-x\) 加到自身可能会使得变大变小,因为 \(a_i<x\) 或者 \(a_i>x\) 都有可能能激活边。

在讨论之前,我们需要明确一点,那就是答案是一棵生成树,当然 \(dfs 树\) 也行,考虑任意一棵都满足吗?答案是肯定的,基于充要条件,我们任意一棵树都可以至少找到一组点对,使得不断缩小为更小问题,那么很显然,我们从任意一棵生成树来讨论这三类边的影响:

如图所示,红色表示已经满足题意的可行边,蓝色表示不可行边(显然不可行边两个端点点权一定都 \(<x\)),当红色边激活后会使得某些蓝色边转化为红色边,然后再次重新激活红色边(当然也有可能某些红色边激活使得其他红色边变为了蓝色边),依次的进行激活所有边。

我们来考虑一个有意思的东西:

很显然叶子节点当除自身以外的所有边都激活时,一定会和剩下的连通块激活。举个例子,比如 \(6-3\) 这条蓝色边,当 \(3\) 与其他所有边除了 \(3-6\) 以外都变成红色了,那么 \(3-6\) 这条蓝色边一定可以变成红色边,基于充要条件。

加强结论:

对于例如 \(3\) 连接的所有叶子节点来说,当除了这些连边以外,与 \(3\) 连接的其他边变成红色以外,例如本图中即为 \(1-3\) 染成了红色以后,剩余的叶子节点边都能 “不计顺序” 地染成红色。

考虑反证法证明:

基于上述的三类边,显然当染色成红色边以后可大可小,大的情况为另一个和他染色的节点为 \(>x\),否则为 \(<x\)。而此时除了叶子节点以外对 \(3\) 已无影响,并且此时此刻的所有需要判断的叶子节点只有 “原本为” 蓝色边叶子节点,红色边我们已经能激活的全部激活了,所以这意味着这些剩余的叶子节点一定都是 \(<x\) 的,即对这个父节点影响是 “减小”,并不存在增大。

我们考虑依次操作叶子节点的影响,假如反复使得它变小,一直到某个叶子结点 \(a_i+a_3\) 无法染成红色,那显然这等价于无解,显然这个是不可能的,基于上述说的充要条件一定有解。所以叶子节点无论顺序,一定都能被染成红色边。

考虑子问题,把所有叶子节点放在最后一次染色,这等价于生成树少了一层,而其他情况就是正儿八经地能连就连,这样一来原图少一层就变为了:

这其实是一模一样的子问题(当然可能有蓝色边变为了红色边,而红色边变为了蓝色边,因为可大可小)。所以我们得到了最终的策略,从下往上贪心,每一层的叶子节点放在最后进行激活,能够激活的点优先激活,这样一来任意一棵 \(DFS\) 树我们都能正确地构造出答案了。

参照代码
#include <bits/stdc++.h>

// #pragma GCC optimize("Ofast,unroll-loops")
// #pragma GCC optimize(2)

// #define isPbdsFile

#ifdef isPbdsFile

#include <bits/extc++.h>

#else

#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>

#endif

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
typedef __int128 i128;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

template <typename T>
int disc(T* a, int n)
{
    return unique(a + 1, a + n + 1) - (a + 1);
}

template <typename T>
T lowBit(T x)
{
    return x & -x;
}

template <typename T>
T Rand(T l, T r)
{
    static mt19937 Rand(time(nullptr));
    uniform_int_distribution<T> dis(l, r);
    return dis(Rand);
}

template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
    return (a % b + b) % b;
}

template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
    a %= c;
    T1 ans = 1;
    for (; b; b >>= 1, (a *= a) %= c)if (b & 1)(ans *= a) %= c;
    return modt(ans, c);
}

template <typename T>
void read(T& x)
{
    x = 0;
    T sign = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')sign = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    x *= sign;
}

template <typename T, typename... U>
void read(T& x, U&... y)
{
    read(x);
    read(y...);
}

template <typename T>
void write(T x)
{
    if (typeid(x) == typeid(char))return;
    if (x < 0)x = -x, putchar('-');
    if (x > 9)write(x / 10);
    putchar(x % 10 ^ 48);
}

template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
    write(x), putchar(c);
    write(c, y...);
}


template <typename T11, typename T22, typename T33>
struct T3
{
    T11 one;
    T22 tow;
    T33 three;

    bool operator<(const T3 other) const
    {
        if (one == other.one)
        {
            if (tow == other.tow)return three < other.three;
            return tow < other.tow;
        }
        return one < other.one;
    }

    T3() { one = tow = three = 0; }

    T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
    {
    }
};

template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
    if (x < y)x = y;
}

template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
    if (x > y)x = y;
}

constexpr int N = 3e5 + 10;
ll sumA;
ll a[N];
int n, m;
int front, last; //前面放可以先激活的边,后面放下层的叶子节点连边
int ans[N];
vector<pii> child[N];
ll x;
bool vis[N];

inline void dfs(const int curr)
{
    vis[curr] = true;
    for (const auto [nxt,id] : child[curr])
    {
        if (vis[nxt])continue;
        dfs(nxt);
        if (a[curr] + a[nxt] >= x)a[curr] += a[nxt] - x, ans[front++] = id;
        else ans[last--] = id;
    }
}

inline void solve()
{
    cin >> n >> m >> x;
    forn(i, 1, n)cin >> a[i], sumA += a[i];
    forn(i, 1, m)
    {
        int u, v;
        cin >> u >> v;
        child[u].emplace_back(v, i);
        child[v].emplace_back(u, i);
    }
    if (sumA < (n - 1) * x)
        no << endl;
    else
    {
        yes << endl;
        front = 1, last = n - 1;
        dfs(1);
        forn(i, 1, n-1)cout << ans[i] << endl;
    }
}

signed int main()
{
    // MyFile
    Spider
    //------------------------------------------------------
    // clock_t start = clock();
    int test = 1;
    //    read(test);
    // cin >> test;
    forn(i, 1, test)solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
    // clock_t end = clock();
    // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

\[时间复杂度为:\ O(n+m) \]

posted @ 2024-01-26 12:21  Athanasy  阅读(45)  评论(0编辑  收藏  举报