P3571 [POI2014] - Supercomputer 题解
您已获得最佳的阅读体验!
集训时讲到了这个题,刚好写篇题解记录一下思路。
题目地址:P3571
题目难度:NOI/NOI+/CTSC
给定一棵
个节点的有根树,根节点为 。 次询问,每次给定一个 ,用最少的操作次数遍历完整棵树,输出最少操作次数。每次操作可以选择访问不超过 个未访问的点,且这些点的父亲必须在这次操作之前被访问过。
。
这道题其实有一个裸结论可以套:
对于一棵树,一定存在一个最优解,满足:恰好用
步操作删除完树上深度小于等于 的节点,此后一定可以通过每次删除 个节点把整棵树的所有节点删除完毕。
形式化地,令
此时把它看作动态规划的转移方程,移项可以得到
重点在于如何证明转移方程的正确性。要想证明一个相等关系,可以从
首先来证
再来证
同样的思路,先证
而不等号左侧用两个前缀和相减代表所有深度介于
接下来证
综上所述,
最后就是套斜率优化的模板了(有现成的公式还真是方便呢):
#include <bits/stdc++.h>#define N 1000010using namespace std;
int dp[N];int dep[N], s[N];int query[N];int q[N];int hh = 0, tt = 0;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int n, m; int maxd = 0, maxq = 0; cin >> n >> m; for (int i = 1; i <= m; i++) { cin >> query[i]; maxq = max(maxq, query[i]); } s[1] = dep[1] = 1; for (int i = 2; i <= n; i++) { int x; cin >> x; dep[i] = dep[x] + 1; // 其实建单向边用DFS处理节点深度也是可以的,只是这道题并没这个必要? s[dep[i]]++; maxd = max(maxd, dep[i]); } for (int i = maxd; i; i--) s[i] += s[i + 1]; // 处理 i 层及更深层的节点总数 // 单调队列维护凸包 for (int i = 1; i <= maxd; i++) { // 用交叉相乘法以免出题人卡精度 while (hh < tt && (s[q[tt] + 1] - s[i + 1]) * (q[tt] - q[tt - 1]) < (s[q[tt] + 1] - s[q[tt - 1] + 1]) * (q[tt] - i)) tt--; q[++tt] = i; } for (int i = 1; i <= maxq; i++) { while (hh < tt && (s[q[hh] + 1] - s[q[hh + 1] + 1]) < -i * (q[hh] - q[hh + 1])) hh++; int j = q[hh]; dp[i] = j + (s[j + 1] + i - 1) / i; // 套转移方程 } for (int i = 1; i <= m; i++) cout << dp[query[i]] << ' '; cout << endl; return 0;}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!