P5628 【AFOI-19】面基 (dp + 容斥)
P5628 【AFOI-19】面基 (dp + 容斥)
题目传送门
题目大意:略
题目分析:
首先我们观察数据范围,我们发现给定的图是一棵树,那么我们可以直接 \(dfs\) 来计算重要度,根据乘法原理可知。对于某条边的重要度为边两侧的节点个数的乘积。
接下来我们考虑用 \(dp\) 来进行求解,我们令 \(f_{i,j}\) 为以 \(i\) 为根,与 \(i\) 距离小于 \(j\) 的重要度。那么我们可以很容易的得出转移方程
\(f_{u,k} = \sum_{i \in son} f_{i,k-1} + val\)
那么转移方程是否正确呢?答案是否定的,因为在这个转移方程中,只考虑了孩子,而没有考虑父亲,所以我们还需要将父亲的重要度加上,我们假设当前应该转移 \(f_{u,k}\),那么我们需要加上 \(f_{fa,k-1}\),但是这样的话,以 \(u\) 为根的子树又加了一次,所以我们还需要减掉 \(f_{u,k-2}\)。至此,我们可以得出正确的转移方程:
\(f_{u,k} = \sum_{i \in son} f_{i,k-1} + val + f_{fa,k-1} - f_{u,k-2}\)
代码实现:
View code
#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int M = 3e4 + 7 , N = 2e2 + 7;
struct T{int v , val;};
vector<T> e[M];
int dp[M][N] , size[M] , n , k;
void dfs(int x , int fa) {
size[x] = 1;
for (auto &i : e[x]) {
if(i.v == fa) continue;
dfs(i.v , x);
i.val = size[i.v] * (n - size[i.v]);
size[x] += size[i.v];
}
}
void dfs_1(int x , int fa) {
dp[x][0] = 0;
for (auto i : e[x]) {
if(i.v == fa) continue;
dfs_1(i.v , x);
for(int j = 1; j <= k + 1; ++ j)
dp[x][j] += dp[i.v][j - 1] + i.val;
}
}
void dfs_2(int x , int fa , int Val) {
if(fa != 0) {
for(int j = k + 1; j > 1; -- j) dp[x][j] += dp[fa][j - 1] - dp[x][j - 2];
dp[x][1] += Val;
}
for(auto i : e[x]) {
if(i.v == fa) continue;
dfs_2(i.v , x , i.val);
}
}
signed main () {
ios::sync_with_stdio(0),cin.tie(0);
cin >> n >> k;
for(int i = 1; i < n ; ++ i) {
int u , v; cin >> u >> v;
e[u].pb({v , 0}) , e[v].pb({u , 0});
}
dfs(1 , 0);
dfs_1(1 , 0);
dfs_2(1 , 0 , 0);
int ans = 0;
for(int i = 1; i <= n; ++ i) ans = (ans > dp[i][k + 1] ? ans : dp[i][k + 1]);
cout << ans;
}
但是这道题为什么直接用 \(dp\) 的呢。
- [\(1\)]: 这道题给作者的第一感觉应该是一个树上 \(dp\) ,所以作者直接向 \(dp\) 的方向想了。
- [\(2\)]: 这道题是求出最优(最不优)的,那么会首先考虑到 \(dp\) 或 \(贪心\)。