最近比较系统地练了练基环树的题,最后在这里总结一波,留一点方法与套路。

首先,基环树的模型应该是比较明显的。和树类比,除了题目中给出一棵树之类的这种很直接的方式,树的有关模型,较常见的有根据某个性质,我们可以得到除了根每个点都能找到唯一对应的父亲。

而基环树除了给出$n$个点$n$条边,比较明显的有每个点对应了一个出点,这样就构成了一棵基环树森林。

大概除了毒瘤题之外,基环树上做做dp就差不多了。

dp的方法一般有两种,本质都是先在子树内dp好,然后扣环,下面只考虑环上的处理:

  1. 一种是边上带有限制的,一般体现在相邻两个点的某些限制。这时可以随便在环上选一条边,枚举限制生不生效,直接做树形dp就行了。
  2. 另一种是统计类的,比如求直径之类的。这时通常断环为链,有时需要再复制一遍,在链上dp。

第一种类型的一个典型例子就是2018牛客多校的某题。

题目概述:有$n$件物品,每件物品有一个价格和折扣,两个优惠,可以选择使用折扣或者选择不折扣而送一个其他物品,被送物品不能使用优惠,问凑齐所有物品的最小花费。

每件物品对应了一个附赠的物品,很让人联想到基环树,而且树边上有限制。用$f_{i,0}$表示得到了$i$子树内的物品的最小花费,$f_{i,1}$表示得到了$i$子树内的物品且第$i$件物品不是送来的最小花费。传统的树形dp之后,给不给环上的第一个点限制就决定了环上最后一个点的状态,做两次dp就可以了。

#include <cstdio>
#include <queue>
#include <iostream>
 
using namespace std;
 
typedef long long LL;
const int N = 100005;
const LL INF = 1e17 + 7;
 
int n;
int p[N], d[N], to[N], in[N];
int flg[N], vis[N], st[N], tp;
LL ans, f0[N], f1[N], g0[N], g1[N];
vector<int> e[N];
queue<int> Q;
 
void Dfs(int x) {
  f0[x] = p[x] - d[x];
  f1[x] = p[x];
  for (int v : e[x]) {
    if (flg[v]) continue;
    Dfs(v);
    f0[x] += f0[v];
    f1[x] += f0[v];
  }
  for (int v : e[x]) {
    if (flg[v]) continue;
    f0[x] = min(f0[x], f1[x] - p[x] - f0[v] + f1[v]);
  }
}
 
LL Solve() {
  static LL re;
  g0[1] = f0[st[1]]; g1[1] = f1[st[1]];
  for (int i = 2; i <= tp; ++i) {
    g1[i] = g0[i - 1] + f1[st[i]];
    g0[i] = min(g1[i - 1] + f1[st[i]] - p[st[i]], g0[i - 1] + f0[st[i]]);
  }
  re = g0[tp];
  g0[1] = f1[st[1]] - p[st[1]]; g1[1] = INF;
  for (int i = 2; i <= tp; ++i) {
    g1[i] = g0[i - 1] + f1[st[i]];
    g0[i] = min(g1[i - 1] + f1[st[i]] - p[st[i]], g0[i - 1] + f0[st[i]]);
  }
  return min(re, g1[tp]);
}
 
int main() {
  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", &to[i]);
    ++in[to[i]]; flg[i] = 1;
    e[to[i]].push_back(i);
  }
  for (int i = 1; i <= n; ++i) {
    if (!in[i]) Q.push(i);
  }
  for (; !Q.empty(); ) {
    int x = Q.front(); Q.pop();
    flg[x] = 0;
    if (--in[to[x]] == 0) Q.push(to[x]);
  }
   
  for (int i = 1; i <= n; ++i) {
    if (flg[i] && !vis[i]) {
      vis[i] = 1; st[tp = 1] = i;
      Dfs(i);
      for (int t = to[i]; t != i; t = to[t]) {
        vis[t] = 1; st[++tp] = t;
        Dfs(t);
      }
      ans += Solve();
    }
  }
  printf("%lld\n", ans);
 
  return 0;
}
View Code

相关文章:

  • 2022-12-23
  • 2021-05-24
  • 2022-01-24
  • 2022-02-24
  • 2022-01-20
  • 2021-07-16
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2021-04-06
  • 2022-12-23
  • 2021-04-05
相关资源
相似解决方案