牛客网暑期ACM多校训练营(第二场)
n种物品,两种买法,一种选择折扣价,一种选择原价购买,使得自己的父亲可以免费。求最小值买下全部物品。
基环内向树DP。先找到树上的环,对于环上的每个点悬挂的树都跑一次树DP。然后断环为链,跑一次链DP。链上考虑两种情况,断开的边为u->v,分别考虑u对v有贡献和u对v无贡献,之后v->v1->…->u DP得到对应的最小值,即circle[u][1]和circle[u][0]。
比赛的时候现学基环内向树DP失败
#include<bits/stdc++.h>
using namespace std;
#define lc o<<1
#define rc o<<1|1
#define fi first
#define se second
#define pb push_back
#define ALL(X) (X).begin(), (X).end()
#define bcnt(X) __builtin_popcountll(X)
#define CLR(A, X) memset(A, X, sizeof(A))
#pragma comment(linker, "/STACK:1024000000,1024000000")
#define DEBUG printf("Passing [%s] in Line %d\n",__FUNCTION__,__LINE__)
using DB = double;
using LL = long long;
using PII = pair<int, int>;
const int N = 1e5+10;
const LL INF = 1e18;
bool cir[N];
int p[N], d[N], f[N], vis[N];
LL dp[N][2], c[N][2], sum[N];
vector<int> G[N], g;
void dfs(int u, int fa) {
vis[u] = 1;
LL sum = 0, tmp = INF;
for(int v:G[u]) if(v != fa) {
if(cir[v]) continue;
dfs(v, u);
sum += dp[v][0];
tmp = min(tmp, dp[v][1]-dp[v][0]);
}
::sum[u] = sum;
dp[u][0] = min(sum+p[u]-d[u], sum+tmp);
dp[u][1] = sum+p[u];
}
LL solve(int u) {
while(!vis[u]) { vis[u] = 1, u = f[u]; }
g.clear();
while(vis[u] == 1) { vis[u] = 2, cir[u] = 1; g.pb(u), u = f[u]; }
for(int u:g) dfs(u, 0);
LL ret = INF;
for(int x = 0; x < 2; x++) {
c[0][0] = x?sum[g[0]]:dp[g[0]][0];
c[0][1] = dp[g[0]][1];
for(int i = 1; i < g.size(); i++) {
c[i][1] = c[i-1][0]+dp[g[i]][1];
c[i][0] = min(c[i-1][0]+dp[g[i]][0], c[i-1][1]+sum[g[i]]);
}
ret = min(ret, c[g.size()-1][x]);
}
return ret;
}
int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", p+i);
for(int i = 1; i <= n; i++) scanf("%d", d+i);
for(int i = 1; i <= n; i++) {
scanf("%d", f+i);
G[f[i]].pb(i);
}
LL ans = 0;
for(int i = 1; i <= n; i++) if(!vis[i]) {
ans += solve(i);
}
printf("%lld\n", ans);
return 0;
}