P7201 [COCI 2019/2020 #1] Džumbus 题解
_Spectator_ · · 题解
可能更好的食用体验
{\color{#00CD00}\text{题意}}
给定一个
增加一个虚拟结点
注意到
接下来思考如何转移。假设当前结点为
-
f_{u,i,0} + f_{v,j,0} \to f_{u,i+j,0} -
f_{u,i,0} + f_{v,j,1} \to f_{u,i+j,0} -
f_{u,i,0} + f_{v,j,2} \to f_{u,i+j,0} -
f_{u,i,1} + f_{v,j,0} \to f_{u,i+j,1} -
f_{u,i,1} + f_{v,j,1} \to f_{u,i+j+2,2} -
f_{u,i,1} + f_{v,j,2} \to f_{u,i+j+1,2} -
f_{u,i,2} + f_{v,j,0} \to f_{u,i+j,2} -
f_{u,i,2} + f_{v,j,1} \to f_{u,i+j+1,2} -
f_{u,i,2} + f_{v,j,2} \to f_{u,i+j,2}
稍微整理如下:
-
f_{u,i+j,0}\gets f_{u,i,0} + \min(f_{v,j,0}, f_{v,j,1}, f_{v,j,2}) -
f_{u,i+j,1}\gets f_{u,i,1} + f_{v,j,0} -
f_{u,i+j,2}\gets f_{u,i,2} + \min(f_{v,j,0}, f_{v,j,2}) -
f_{u,i+j+1,2}\gets \min(f_{u,i,1} + f_{v,j,2}, f_{u,i,2} + f_{v,j,1}) -
f_{u,i+j+2,2}\gets f_{u,i,1} + f_{v,j,2}
询问相当于找最大的
具体实现参见代码。时间复杂度为
{\color{#00CD00}\text{代码}}
#include <bits/stdc++.h>
#define int long long
#define MIN(a, b) a = min(a, b)
using namespace std;
const int N = 1e3 + 5, inf = 1e18;
long long n, m, q, root = 0;
int a[N], sz[N], mi[N], vis[N];
vector<int> G[N];
void DFS(int u, int fa){
vis[u] = true;
for(int v: G[u]) if(v != fa) DFS(v, u);
}
int f[N][N][3];
//f[i][j][0/1/2]: 以 i 为根的子树中,有 j 个贡献,结点 i 不选/选了但无贡献/选了且有贡献的最小代价
void dfs(int u, int fa){ //树形 DP
sz[u] = 1;
f[u][0][0] = 0, f[u][0][1] = a[u];
for(int v: G[u]){
if(v == fa) continue;
dfs(v, u);
for(int i=sz[u]; i>=0; i--){
for(int j=sz[v]; j>=0; j--){ //dp 转移
MIN(f[u][i+j][0], f[u][i][0] + min({f[v][j][0], f[v][j][1], f[v][j][2]}));
MIN(f[u][i+j][1], f[u][i][1] + f[v][j][0]);
MIN(f[u][i+j][2], f[u][i][2] + min(f[v][j][0], f[v][j][2]));
MIN(f[u][i+j+1][2], min(f[u][i][1] + f[v][j][2], f[u][i][2] + f[v][j][1]));
MIN(f[u][i+j+2][2], f[u][i][1] + f[v][j][1]);
}
}
sz[u] += sz[v];
}
}
signed main(){
cin.tie(nullptr) -> sync_with_stdio(false);
cin >> n >> m; a[root] = inf; //将虚根的代价设为无穷大,避免被选
for(int i=1; i<=n; i++) cin >> a[i];
for(int i=1; i<=m; i++){
int u, v; cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
}
for(int i=1; i<=n; i++) if(!vis[i]){ //将森林连城一棵树
G[root].push_back(i), DFS(i, root);
}
memset(f, 0x3f, sizeof(f)); dfs(root, -1);
mi[n+1] = inf; //mi[i] 表示获得 i 个贡献的最小代价
for(int i=n; i>=1; i--) mi[i] = min(mi[i+1], f[root][i][0]);
for(cin >> q; q --> 0;){
int s; cin >> s;
cout << upper_bound(mi+1, mi+1+n, s) - mi - 1 << "\n"; //二分查询答案
}
return 0;
}