【luogu CF1119F】【luogu P7600】Niyaz and Small Degrees / 封闭道路

Niyaz and Small Degrees / 封闭道路

题目链接:luogu CF1119F / luogu P7600

题目大意

给一棵树,边有边权,是砍掉的费用。
对于每个 x,询问最少要花费费用砍边,使得每个点都不与超过 x 个点相连。

思路

首先看到题目容易想到要 DP。
那我们就想想 n2logn 的 DP,我们枚举 x,然后搞 O(nlogn) DP。

那就是设 fi,0/1i 与父亲连边删或不删时以 i 为根的子树的点都满足度数不大于 x 的最小费用。
在度数没有限制的时候,那对于 u 的儿子 v,之间边权为 w,有 fu,0=fu,1=min{fv,0,fv,1+w}

那如果有了度数限制,那我们就要想,你选左边就是割,右边就是不割。
那对于 fu,0,它就一定要选 duux 个左边的。(duuu 的度数)
那对于 fu,1,它就一定要选 duux1 个左边的。
那容易想到,如果本来就是左边优,那我们肯定选左边。那我们可以提前选,然后记得要选的个数要减。
那如果是右边优,那我们就看如果硬要选左边会亏多少(fv,1+wfv,0),那我们肯定优先硬改亏的最少的,那我们可以用堆来找到前 duux 个的总费用。
那就可以了。

那我们考虑怎么优化。
不难想到,如果 xduu,那 u 这个点割不割都无所谓,那我们可以把这个点删掉。
但不能把别的点入度修改,而且别的点就相当于有一个费用是 ww 是它与这个点的边的边权)放进了堆里面。而且这个是一只存在的,不像我们上面算法的堆,那些要清空,而这些要留下。

那我们就要弄一个可删堆,可以用两个普通的堆来维护。
(大概就是你要删你就不直接删而是放进删除堆,每次你要取之前如果你的普通堆和删除堆堆顶相同就把它弹出不要,即我们不立刻删掉,而是阻碍到再删)
(具体是看看代码)

那我们接着继续想,那每次枚举 x,DP 的规模就不是 nlogn,而是 mlogmm 时度数超过 x 的点数)
那这个所有 m 加起来的规模其实是 O(n) 级别的,因为你想,入度和是 2n,那一个点入度是 x 就会贡献 x 次,那总共就最多贡献 2n 次,所以 m 就是 O(n) 级别。
复杂度也就变成了 O(nlogn)

具体的实现可以看看代码。

代码

由于两道题是一模一样的,而且 APIO 由于那个交互代码会有点乱,这里就只在 CF 的那个上面写注释。

然后记得两个的点编号一个是 1n,一个是 0n1

Niyaz and Small Degrees

#include<queue> #include<cstdio> #include<vector> #include<algorithm> #define ll long long using namespace std; int n, x, y, z, du[250001], xx[250001]; int nd, kil, in[250001]; vector <pair<int, int> > e[250001]; ll sum, f[250001][2], re; vector <ll> ans, tmp, del; struct cd_heap {//可删堆 priority_queue <int> q1, q2; ll sum; void insert(int x) {q1.push(x); sum += x;} void delete_(int x) {q2.push(x); sum -= x;} void ck() {while (!q1.empty() && !q2.empty() && q1.top() == q2.top()) q1.pop(), q2.pop();} int top() {ck(); return q1.top();} void pop() {ck(); sum -= q1.top(); q1.pop();} int size() {return q1.size() - q2.size();} }h[250001]; void add(int x, int y, int z) { e[x].push_back(make_pair(y, z)); du[x]++; } bool cmp0(pair <int, int> x, pair <int, int> y) { return du[x.first] > du[y.first]; //优化,枚举相连的点时按相连点入度从大到小枚举 //如果枚举到已被删去,那后面的点都被删了,就可以直接退出了 } bool cmp(int x, int y) { return du[x] < du[y]; } void kill(int x) {//删点 for (int i = 0; i < e[x].size(); i++){ int y = e[x][i].first, z = e[x][i].second; if (du[y] <= nd) break; h[y].insert(z);//相连的边要留着,相当于单独费用是 z 的(因为不砍不会有花费,z-0=z) } } void dfs(int now, int father) { in[now] = nd; int num = du[now] - nd;//看要砍多少边 while (h[now].size() > num)//维护堆只有这么多个边(下同) h[now].pop(); for (int i = 0; i < e[now].size(); i++) {//dfs 递归 DP int to = e[now][i].first; if (du[to] <= nd) break; if (to == father) continue; dfs(to, now); } tmp.clear(); del.clear();//记得清空 for (int i = 0; i < e[now].size(); i++) { int to = e[now][i].first, dis = e[now][i].second; if (du[to] <= nd) break; if (to == father) continue; ll x = f[to][1] + dis - f[to][0]; if (x <= 0) {//明显是不割优 re += f[to][1] + dis; num--;//这个直接处理乘割了,那要割的数量就少了 } else { re += f[to][0]; del.push_back(x);//把这里加进堆的记录下来,因为只有这一次可以用,那我们搞完要删掉它们 h[now].insert(x); } } //tmp 是存丢出去的,由于也是只是这一次用,那我们到时还要放回去,丢出去只是为了统计费用 while (h[now].size() && h[now].size() > num) tmp.push_back(h[now].top()), h[now].pop(); f[now][0] = h[now].sum;//不删父亲的 while (h[now].size() && h[now].size() > num - 1)//删父亲就代表要多删一条 tmp.push_back(h[now].top()), h[now].pop(); f[now][1] = h[now].sum;//删父亲的 for (int i = 0; i < tmp.size(); i++)//记得把要还原的还原,把要删的删了 h[now].insert(tmp[i]); for (int i = 0; i < del.size(); i++) h[now].delete_(del[i]); } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { scanf("%d %d %d", &x, &y, &z); add(x, y, z); add(y, x, z); sum += z; } ans.push_back(sum); for (int i = 1; i <= n; i++) { sort(e[i].begin(), e[i].end(), cmp0); xx[i] = i; } sort(xx + 1, xx + n + 1, cmp); kil = 1; while (++nd < n) { while (kil <= n && nd == du[xx[kil]]) kill(xx[kil]), kil++;//有新的可以直接删掉的点 if (kil > n) {//所有点都被删掉了,后面答案都是 0 ans.push_back(0); continue; } re = 0; for (int i = kil; i <= n; i++) {//DP,记得要枚举每个树 if (in[xx[i]] == nd) continue; dfs(xx[i], 0); re += f[xx[i]][0]; } ans.push_back(re); } for (int i = 0; i < ans.size(); i++) printf("%lld ", ans[i]); return 0; }

封闭道路

#include<queue> #include<cstdio> #include<vector> #include<algorithm> #define ll long long using namespace std; int n, x, y, z, du[250001], xx[250001]; int nd, kil, in[250001]; vector <pair<int, int> > e[250001]; ll sum, f[250001][2], re; vector <ll> ans, tmp, del; struct cd_heap { priority_queue <int> q1, q2; ll sum; void insert(int x) {q1.push(x); sum += x;} void delete_(int x) {q2.push(x); sum -= x;} void ck() {while (!q1.empty() && !q2.empty() && q1.top() == q2.top()) q1.pop(), q2.pop();} int top() {ck(); return q1.top();} void pop() {ck(); sum -= q1.top(); q1.pop();} int size() {return q1.size() - q2.size();} }h[250001]; void add(int x, int y, int z) { e[x].push_back(make_pair(y, z)); du[x]++; } bool cmp0(pair <int, int> x, pair <int, int> y) { return du[x.first] > du[y.first]; } bool cmp(int x, int y) { return du[x] < du[y]; } void kill(int x) { for (int i = 0; i < e[x].size(); i++){ int y = e[x][i].first, z = e[x][i].second; if (du[y] <= nd) break; h[y].insert(z); } } void dfs(int now, int father) { in[now] = nd; int num = du[now] - nd; while (h[now].size() > num) h[now].pop(); for (int i = 0; i < e[now].size(); i++) { int to = e[now][i].first; if (du[to] <= nd) break; if (to == father) continue; dfs(to, now); } tmp.clear(); del.clear(); for (int i = 0; i < e[now].size(); i++) { int to = e[now][i].first, dis = e[now][i].second; if (du[to] <= nd) break; if (to == father) continue; ll x = f[to][1] + dis - f[to][0]; if (x <= 0) { re += f[to][1] + dis; num--; } else { re += f[to][0]; del.push_back(x); h[now].insert(x); } } while (h[now].size() && h[now].size() > num) tmp.push_back(h[now].top()), h[now].pop(); f[now][0] = h[now].sum; while (h[now].size() && h[now].size() > num - 1) tmp.push_back(h[now].top()), h[now].pop(); f[now][1] = h[now].sum; for (int i = 0; i < tmp.size(); i++) h[now].insert(tmp[i]); for (int i = 0; i < del.size(); i++) h[now].delete_(del[i]); } std::vector<long long> minimum_closure_costs(int N, std::vector<int> U, std::vector<int> V, std::vector<int> W) { n = N; for (int i = 1; i < n; i++) { x = U[i - 1] + 1; y = V[i - 1] + 1; z = W[i - 1]; add(x, y, z); add(y, x, z); sum += z; } ans.push_back(sum); for (int i = 1; i <= n; i++) { sort(e[i].begin(), e[i].end(), cmp0); xx[i] = i; } sort(xx + 1, xx + n + 1, cmp); kil = 1; while (++nd < n) { while (kil <= n && nd == du[xx[kil]]) kill(xx[kil]), kil++; if (kil > n) { ans.push_back(0); continue; } re = 0; for (int i = kil; i <= n; i++) { if (in[xx[i]] == nd) continue; dfs(xx[i], 0); re += f[xx[i]][0]; } ans.push_back(re); } return ans; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P7600.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(52)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示