window.cnblogsConfig = {//可以放多张照片,应该是在每一个博文上面的图片,如果是多张的话,那么就随机换的。 homeTopImg: [ "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png", "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png" ], }

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;
}
posted @ 2024-01-18 07:49  CCF_IOI  阅读(26)  评论(0)    收藏  举报