题解:P6594 [YsOI2020] 换寝室

· · 题解

提示部分

建议按顺序看提示。

::::info[1.使用算法]

二分与 DP。

其实还有树上差分,不过是辅助用的。

::::

::::info[2.二分什么值]

二分答案小朋友们的总不满意值。

此值满足二分单调性。

::::

::::info[3.DP 状态设定]

:::: ::::info[4.DP 转移] 目前讨论 $x$,定讨论 $x$ 的孩子 $y$ 对 $dp_{x,j}$ 的贡献。 贡献为: $$ \min(dp_{y,j}, Min_y + w(x,y)) $$ 其中 $Min_y$ 代表 $\displaystyle \min_{j=1}^{n} dp_{y,j}$,$w(x,y)$ 代表这条边被几个老师包含。 :::: 若你把所有提示内容都想正确,那么你的做法大概率正确。 但是你如果看不懂或者没想出来,请看下面的讲解。 ## 讲解 ### 使用算法 本题使用二分与动规,还有树上差分辅助处理老师路径。 为何使用二分?因为若小朋友们的总不满意值变大,那么所有老师的不满意值之和会变小。 ### 二分什么值 从上文。 > 若小朋友们的总不满意值变大,那么所有老师的不满意值之和会变小。 可以看出可以二分小朋友们的总不满意值。 ### DP 状态设定 如何判断目前 $mid$ 是否合法?我们将使用树上 DP 算法处理。 若只设 $dp_i$ 代表讨论到 $i$ 为根的子树,最小老师的不满意值之和。 可发现无法处理最大值与最小值。 但若设 $dp_{i,j,k}$ 代表讨论到 $i$ 为根的子树,与 $i$ 相连的连通块的值中,最大值为 $h_j$,最小值为 $h_k$,最小老师的不满意值之和。但是会超空间。 注意到连通块的值在 $mid$ 的限制下可视为一个区间,即某连通块的值可以在一个长度为 $mid$ 的区间中(但是不一定要占满),此时可以得出状态。 $dp_{i,j}$ 代表讨论到 $i$,与 $i$ 相连的连通块的值在 $[h_j - mid, h_j]$ 之间,最小老师的不满意值之和。 ### DP 转移 目前讨论 $x$,定讨论 $x$ 的孩子 $y$ 对 $dp_{x,j}$ 的贡献。 对于 $(x,y)$ 这条边有两种取法,取或不取。 $dp_{x,j}$ 加上两种取法中最小值即可。 #### 取 必定只能由 $dp_{y,j}$ 而来,第二维的值必须一样,即区间一样。 因为如果某情况在状态 $[h_k - mid, h_k](k \ne j)$ 中没有占满,此时可以为 $[h_j - mid, h_j]$ 更新,但是状态 $[h_j - mid, h_j]$ 也一定考虑了这种状态,所以 $[h_j - mid, h_j]$ 一定满足且最值。 若取状态 $[h_k - mid, h_k](k \ne j)$,那么可能会超出范围,而且不值,所以只从 $dp_{y,j}$ 而来。 #### 不取 这就是显而易见的了,若不取,上一个连通块是啥我们也不用管,加上权值即可。 #### 转移部分代码 ``` MAX为极大值 Min[x] = MAX; for (int j = 1; j <= n; j++){ for (int i = 0; i < a[x].size(); i++){ int y = a[x][i]; dp[x][j]+=min(dp[y][j],Min[y]+sum[y]); dp[x][j] = min(dp[x][j],MAX); } Min[x] = min(Min[x],dp[x][j]); } ``` ### 其他杂事 初始化 DP 就是看看 $i (1 \le i \le n)$ 是否可以与 $x$ 在一起,即若 $h_x$ 不在 $i$ 所对应的区间里,设为无穷大。 ### 代码 ``` #include <bits/stdc++.h> using namespace std; const int MAX = 1e9; int h[803];int n,m,k; vector <int> a[808]; int sum[808],flag[808]; int fa[808][30]; int d[808]; int Min[808]; int dp[808][808];//讨论到i,与i相连的连通块的值在[h[j]-mid,h[j]]之间,老师的不满意值之和 void dfs(int x,int f,int id){ if (id == 1) sum[x] = flag[x]; if (id == 0) { d[x] = d[f]+1; fa[x][0] = f; for (int i = 1; (1<<i) <= d[x]; i++) fa[x][i]=fa[fa[x][i-1]][i-1]; } for (int i = 0; i < a[x].size(); i++){ int y = a[x][i]; if (y != f) { dfs(y,x,id); sum[x] += (id?sum[y]:0); } } } void dfs2(int x,int f,int mid){ for (int i = 0; i < a[x].size(); i++){ int y = a[x][i]; if (y != f) dfs2(y,x,mid);} for (int i =1; i <= n; i++){ if (h[i]-mid > h[x] || h[i] < h[x]) dp[x][i] = MAX;//不可能在一起(不在这个区间) else dp[x][i] = 0; } Min[x] = MAX; for (int j = 1; j <= n; j++){ for (int i = 0; i < a[x].size(); i++){ int y = a[x][i]; dp[x][j]+=min(dp[y][j],Min[y]+sum[y]); dp[x][j] = min(dp[x][j],MAX); } Min[x] = min(Min[x],dp[x][j]); } } int lca(int x,int y){ if (d[x] <= d[y]) swap(x,y); for (int i = 21; i >= 0; i--) if (d[x]-(1<<i) >= d[y]) x = fa[x][i]; if (x == y) return x; for (int i = 21; i>= 0; i--) if (fa[y][i] != fa[x][i]) x = fa[x][i],y = fa[y][i]; return fa[x][0]; } int check(int x){ dfs2(1,0,x); return Min[1]; } int main(){ cin >> n >> m >> k; int max_ = 0; for (int i = 1; i <=n; i++) cin >> h[i],max_ = max(max_,h[i]); for (int i = 1; i <= n-1; i++){ int u,v; cin >> u >>v; a[u].push_back(v); a[v].push_back(u); } dfs(1,0,0); for (int i = 1; i <= m; i++){ int x,y; cin >> x >> y; flag[x] ++,flag[y] ++;//标记 flag[lca(x,y)]-=2; } dfs(1,0,1); int l = 0,r = max_,ans = MAX;//简单二分答案小朋友们的总不满意值 while (l <= r){ int mid = (l+r)/2; if (check(mid)<=k) ans = mid,r = mid-1; else l = mid+1; } cout << ans; return 0; } ```