ABC267E解题报告
ABC267E 解题报告
题意
有一个有 \(n\) 个顶点 \(m\) 条边的无向图,每个点有一个点权 \(a_i\),现在你需要进行以下操作 \(n\) 次:
-
选择一个未被删除的点 \(u\)
-
将这个点及其相连的边删除,代价为与它所有直接相连的未被删除的的点的点权之和
现在请你求出删除整个无向图,单次操作代价最大值的最小值。
分析
根据题面,最大值的最小值,很容易想到二分答案,去二分单次操作的代价。
二分答案中的 check 可以直接通过 bfs 搜一遍看是否每个点都符合要求。但在每次 bfs 都统计一遍子节点的点权和很明显就会 TLE,所以这里采用打制表格预处理,在 bfs 前处理出来点权和。
对于 bfs,类似于拓扑排序的思路,把点权和小于 \(mid\) 的节点加入队列中,然后按 bfs 的思路向下一层搜。设当前节点为 \(u\),子节点为 \(v\),则 \(v\) 的点权和要减去 \(a_u\)(因为 \(u\) 已经访问过了)。再判断 \(v\) 是否符合需求,如果符合,那么加入队列。最后判断是否全部节点都被遍历过了,只有全部都能遍历,才满足要求。
在遍历 \(v\) 时,不需要去看其他已经在队列中的节点权值。如果 \(v\) 节点满足要求,那么直接加入,否则在遍历另一个在队列中与 \(v\) 相关的节点 \(u_1\) 时也会减去相应权值,不需要在 \(u\) 一步减去。
不开 long long
见祖宗。
二分时 \(l\) 记得设为 \(0\),警钟敲烂。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m, sum;
int a[200005], s[200005], vis[200005], t[200005];
vector<int> g[200005];
bool check(int mid){//直接bfs
queue<int> q;
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++){
t[i] = s[i];
if (s[i] <= mid){//符合要求加入队列
q.push(i);
vis[i] = 1;
}
}
while (!q.empty()){
int u = q.front(); q.pop();
for (int i = 0; i < g[u].size(); i++){
int v = g[u][i];
t[v] -= a[u];//只看u节点的权值
if (t[v] <= mid && vis[v] == 0){//符合要求加入队列
vis[v] = 1;
q.push(v);
}
}
}
for (int i = 1; i <= n; i++){//判断是否全部遍历过
if (vis[i] == 0) return 0;
}
return 1;
}
signed main(){
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1, u, v; i <= m; i++){
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1; i <= n; i++){//预处理子节点权值和
for (int j = 0; j < g[i].size(); j++) s[i] += a[g[i][j]];
}
int l = 0, r = 1e18;//l为0!!!!!
while (l < r){//二分答案
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << r;
return 0;
}