CF1515F Phoenix and Earthquake 题解
题目链接:CF 或者 洛谷
首先基于一个事实,答案一定是生成树,显然,每次我们都需要连边,每次都会 \(-x\),那么一共会减少 \((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_i+a_j \ge x\)。我们将它取出,点数 \(-1\),总权减少了 \(x\):
而 \(i=2\) 时,显然成立,所以这不仅是一个必要条件,同时还是个充分条件。
考虑构造的强结论
基于上述我们知道的充要条件,即满足那个式子以后,我们每次取出一对满足题意的点对,一定能构造出答案,现在我们来考虑不满足的情况,显然全部都 \(a_i \ge x\) 一定可以随意构造出答案,我们考虑不满足时有:
这个式子一定是成立的,否则找不到当前一对不满足题意的点,我们可以画出它的连边可能性图:
很显然,有三类可能性边:
-
\(a_i,a_j \ge x\),这类显然满足题意。
-
$a_i \ge x,a_j <x $,这类也显然满足题意。
-
\(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;
}