CF1399-Codeforces Round #661 (Div. 3)-E1. Weights Division (easy version)

传送门:题目链接
题意:给定一棵边权树,树的总价值是根节点到所有叶子节点所经过的距离之和 ∑ v ∈ l e a v e s w ( r o o t , v ) ≤ S \sum\limits_{v \in leaves} w(root, v) \le S vleavesw(root,v)S,距离即根节点到叶子节点经过所有边的边权和。给定一个操作:选择任意一条边将其边权值除以二(向下取整)(即边权 w i : = ⌊ w i 2 ⌋ w_i := \left\lfloor\frac{w_i}{2}\right\rfloor wi:=2wi)。让我们求最少的操作次数,使得树的总价值不大于 k k k
思路:我们简单思考一下求树的总价值的过程,显然每一条边被加的次数是固定的,我们假设其为 t i m e s i times_i timesi,那么 t i m e s i times_i timesi的值是从根节点到所有叶子节点经过本条边的次数(也就是从根节点通过这条边可达的叶子数量),这一步我们可以通过 d f s dfs dfs预处理得到(代码中的 d f s 1 dfs1 dfs1),然后把这些边的权值 w i w_i wi和它对应的 t i m e s i times_i timesi存于优先队列(代码中的 d f s 2 dfs2 dfs2),优先队列的排序规则是按 w i − ⌊ w i 2 ⌋ w_i-\left\lfloor\frac{w_i}{2}\right\rfloor wi2wi从大到小排序。然后贪心的选择最大的直到小于 k k k就行了。

AC代码:
#include

using namespace std;
#define ll long long
#define pb push_back
#define rep(i, a, b) for(int i=a;i<=b;i++)
#define frep(i, a, b) for(int i=a;i>=b;i--)
const int N = 1E5 + 10;

ll n, k, vis[N], times[N], vis2[N];

struct edge {
    int val, to;
    edge(int t, int w) {
        val = w, to = t;
    }
};

struct cmp {
    bool operator()(pair<ll, ll> a, pair<ll, ll> b) {
        return (a.first-a.first/2) * a.second < (b.first-b.first/2) * b.second;
    }
};

vector<edge> G[N];
priority_queue<pair<ll, ll>, vector<pair<ll, ll> >, cmp> nums;
ll sum = 0;

void dfs(int x) {
    vis[x] = 1;
    if (G[x].size() == 1 && x > 1) {
        times[x] = 1;
        return;
    } else {
        for (auto i : G[x]) {
            if (!vis[i.to]) {
                dfs(i.to);
                vis[i.to] = 1;
                times[x] += times[i.to];
            }
        }
    }
    return;
}

void dfs2(int x) {
    vis2[x] = 1;
    if (G[x].size() == 1 && x > 1) return;
    else {
        for (auto e:G[x]) {
            if (!vis2[e.to]) {
                nums.push(make_pair(e.val, times[e.to]));
                sum += e.val * times[e.to];
                dfs2(e.to);
            }
        }
    }
    return;
}

int main() {
    //freopen("1.txt","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    string str;
    while (t--) {
        cin >> n >> k;
        for (int i = 0; i <= n; i++) G[i].clear(), times[i] = 0, vis[i] = 0, vis2[i] = 0;
        sum = 0;
        while (!nums.empty()) nums.pop();
        for (int i = 0; i < n - 1; i++) {
            int u, v, w;
            cin >> u >> v >> w;
            G[u].emplace_back(v, w);
            G[v].emplace_back(u, w);
        }
        dfs(1);
        dfs2(1);
        ll cnt = 0;
        pair<ll, ll> mm;
        while (sum > k) {
            mm = nums.top();
            nums.pop();
            cnt++;            
            sum -= (mm.first - mm.first / 2) * mm.second;
            nums.push(make_pair(mm.first / 2, mm.second));
        }
        cout << cnt << endl;
    }
    return 0;
}

写在最后:这题还是写了很久,对于一些细节的处理非常不到位,导致卡了非常久,第一个错误是每次从总价值 s u m sum sum减掉 m a x ( w i ) ∗ t i m e s i max(w_i)*times_i max(wi)timesi之后往优先队列里push的应该是 ⌊ w i 2 ⌋ \left\lfloor\frac{w_i}{2}\right\rfloor 2wi,我写成了 w i − ⌊ w i 2 ⌋ w_i-\left\lfloor\frac{w_i}{2}\right\rfloor wi2wi,这在 w i w_i wi是奇数的时候显然不对;第二个错误是一开始我并没有将 w i w_i wi t i m e s i times_i timesi分开来存,而是简单的把他们的乘积存到优先队列,这样会由下式原因造成误差 w i ∗ t i m e s i − ⌊ w i ∗ t i m e s i 2 ⌋ = /   ( w i − ⌊ w i 2 ⌋ ) ∗ t i m e s i w_i*times_i-\left\lfloor\frac{w_i*times_i}{2}\right\rfloor {=}\mathllap{/\,} (w_i-\left\lfloor\frac{w_i}{2}\right\rfloor)*times_i witimesi2witimesi=/(wi2wi)timesi故大顶堆应同时维护每一条边的权重 w i w_i wi和计算次数 t i m e s i times_i timesi

你可能感兴趣的:(codeforces,队列,算法,dfs)