传送门:题目链接
题意:给定一棵边权树,树的总价值是根节点到所有叶子节点所经过的距离之和 ∑ v ∈ l e a v e s w ( r o o t , v ) ≤ S \sum\limits_{v \in leaves} w(root, v) \le S v∈leaves∑w(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 wi−⌊2wi⌋从大到小排序。然后贪心的选择最大的直到小于 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 wi−⌊2wi⌋,这在 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 wi∗timesi−⌊2wi∗timesi⌋=/(wi−⌊2wi⌋)∗timesi故大顶堆应同时维护每一条边的权重 w i w_i wi和计算次数 t i m e s i times_i timesi。