牛牛补题

牛牛补题

Bustling City

题意:

给定一个\(w_i\)表示 i 号城市每年会往 \(w_i\) 城市迁移一个人,对于每个点而言,求一个最小的年份满足至少有 m 个城市往当前点迁移,如果没有的话 输出 -1 .

思路:

这个题目首先一看就是一颗基环树,也就是 n 个点 n 个边,如果对于每个基环来进行操作的话,我们先处理出环上的点,对于环上每个点都处理出一个 f 表示当前点的最小的代价,然后围着这个环转两圈,为啥要转两圈捏,是因为第一圈可能会更新首尾,需要再来一圈用更新过首来更新其他的。对于不是环上的点,就可以用树上启发式合并,先处理重儿子并且记录下来。在用这个值来更新。

1--预处理出环

//找环
void dfs1(int u) {
  de[u] = ++top;
  stk[de[u]] = u;
  int j = to[u];
  if (!de[j])
    dfs1(j);
  else if (de[j] <= de[u]) {
    int k = 1;
    for (int i = top; i >= de[j]; i--) {
      cir[++idx] = stk[i];
      num[cir[idx]] = k++;
    }
  }
  top--;
}

2--对于不是环上的点找出重儿子

void dfs2(int u) {
  son[u] = 0, mx[u] = 1;
  // cout << u << endl;
  for (int j : v[u]) {
    if (num[j]) continue;
    //如果是环就G
    dfs2(j);
    if (mx[u] < mx[j] + 1) {
      mx[u] = mx[j] + 1;
      son[u] = j;
    }
  }
}

3--求答案

void dfs3(int u) {
  ddfn[u] = ++dfn;// dfs序
    // f表示当前序号能够到达的点的个数。
  f[ddfn[u]] = 1;// 每个值当前的满足条件的就是自己。
  if (son[u]) dfs3(son[u]), ans[u] = min(ans[u], ans[son[u]] + 1);
    // 如果有重儿子就优先更新重儿子。
  for (int j : v[u]) {
      //枚举其他的儿子
    if (j == son[u] || num[j]) continue;
    dfs3(j);
    for (int k2 = 1; k2 <= mx[j]; k2++) {
        // 这里 就是最深的深度,找到这个点的最大深度,然后进行更新
        //f 表示到 u 点 距离 为 k 的方案数
      int tmp = (f[ddfn[u] + k2] += f[ddfn[j] + k2 - 1]);
        // 如果大于m 就可以
      if (tmp >= m) {
        ans[u] = min(ans[u], k2);
      }
    }
  }
}

Code

const int N = 2e6 + 100, mod = 1e9 + 7, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }
//  深度  环上的点   点的编号         dfs 序 栈
int de[N], cir[N], num[N], to[N], dfn, stk[N], top, idx;
int ddfn[N];
// 最大深度  son 下一个节点   f 是记录以 u 为根的答案 ans 是最后的答案
int mx[N], son[N], f[N], ans[N];
vector<int> v[N];  //存反向边
int n, m;
// w是
struct T {
  int w, j, i, k;
  bool operator<(const T& W) const { return w < W.w; }
} t[N], c2;

//找环
void dfs1(int u) {
  de[u] = ++top;
  stk[de[u]] = u;
  int j = to[u];
  if (!de[j])
    dfs1(j);
  else if (de[j] <= de[u]) {
    int k = 1;
    for (int i = top; i >= de[j]; i--) {
      cir[++idx] = stk[i];
      num[cir[idx]] = k++;
    }
  }
  top--;
}

//对于不是环上的点找出重儿子
// 同时找出一个点最多往下面走多少步
void dfs2(int u) {
  son[u] = 0, mx[u] = 1;
  // cout << u << endl;
  for (int j : v[u]) {
    if (num[j]) continue;
    //如果是环就G
    dfs2(j);
    if (mx[u] < mx[j] + 1) {
      mx[u] = mx[j] + 1;
      son[u] = j;
    }
  }
}

//求ans
void dfs3(int u) {
  ddfn[u] = ++dfn;
  f[ddfn[u]] = 1;
    // 优先更新重儿子
  if (son[u]) dfs3(son[u]), ans[u] = min(ans[u], ans[son[u]] + 1);
  for (int j : v[u]) {
    if (j == son[u] || num[j]) continue;
    dfs3(j);
      ///最长的长度是知道的,然后根据这个来更新结果,为什么可以直接+,是因为我们保证了先处理重儿子,所以他们的序号是连续的,这个地方听秒的。
    for (int k2 = 1; k2 <= mx[j]; k2++) {
      int tmp = (f[ddfn[u] + k2] += f[ddfn[j] + k2 - 1]);
      if (tmp >= m) {
        ans[u] = min(ans[u], k2);
      }
    }
  }
}

void solve() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> to[i];
    v[to[i]].ps(i);//反向边,因为最后是讨论一个点有多少个点能够走到。
  }
    // 初始化
  memset(de + 1, 0, n << 2);
  memset(num + 1, 0, n << 2);
  memset(ans + 1, 61, n << 2);
  idx = dfn = 0;
  for (int s = 1; s <= n; s++) {
    if (ddfn[s]) continue;
     // 如果没有走过。。
    idx = 0;
    dfs1(s);
    int x = 0;
    for (int i = 1; i <= idx; i++) {
      int e = cir[i];
      de[e] = 1;
      dfs2(e);
      dfs3(e);
        // 这里是找出所有的点 对于每个点存下他的下标,他在到环上的点一号点的距离
        // 它对应的dfs 序列,它对应的f值,距离要考虑他环上的位置,二号点和一号店的距离是1.
        // 一路累加过来
      for (int j2 = 1; j2 <= mx[e]; j2++) {
        t[++x] = {j2 - 1, (j2 + i - 2) % idx + 1, cir[i], f[ddfn[e] + j2 - 1]};
      }
    }
      
 // 从距离小的开始更新相当于前缀和,保证正确性。
    sort(t + 1, t + 1 + x);
    memset(f + 1, 0, idx << 2);
    for (int j2 = 1; j2 <= x; j2++) {
      T v1 = t[j2];
      int tm = (f[v1.j] += v1.k);
      if (tm >= m) {
        ans[v1.i] = min(ans[v1.i], v1.w);
      }
    }
      
     
//这里说过为啥要循环两次了。
    for (int j = idx - 1; j >= 1; j--) {
      ans[cir[j]] = min(ans[cir[j]], ans[cir[j + 1]] + 1);
    }
    ans[cir[idx]] = min(ans[cir[idx]], ans[cir[1]] + 1);
    for (int j = idx - 1; j >= 1; j--) {
      ans[cir[j]] = min(ans[cir[j]], ans[cir[j + 1]] + 1);
    }
    ans[cir[idx]] = min(ans[cir[idx]], ans[cir[1]] + 1);
  }

  for (int i = 1; i <= n; i++) {
    if (ans[i] <= n) {
      cout << ans[i] << " ";
    } else
      cout << -1 << " ";
  }
}

signed main() {
  kd;
  int _;
  _ = 1;
  // cin>>_;
  while (_--) solve();
  return 0;
}

Boss

题意:

有 n 座城市,m 个人,每个人去到每个城市有个时间,每座城市有一个需要的人数,保证这个需要的人数之和等于m,问最小的时间花费。

思路:

n 很小,考虑记录每个人从 哪座城市到哪座城市的代价,然后先把所有的人都放到 1 号点先,对于 其他的人,就对于 1 号点开始更新,考虑对于每个城市而言,都要从 1 号点放 \(C_i\) 个人到 当前的城市,考虑用spfa进行转移,对于每个点都进行 \(C_i\)次转移。在转移的过程中用优先队列对这个过程进行优化,每次都取出最小值。

Code

const int N = 2e6 + 100, mod = 1e9 + 7, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }

int in[N];//  表示当前点在那个位置
int c[N][15];//表示第 i 个人到 j 号城市的代价
int cnt[N], st[15], d[15], fa[15];
// 每座城市的 数量, st 表示当前城市有没有走过,d 表示从当前是 i 号城市的距离,fa表示是从哪个点转移过来的,最后需要回溯这个转移的过程然后把这些点的边都更新一边,
int n, k;
priority_queue<pi, vector<pi>, greater<pi> > e[12][12];
// 优先队列
int spfa(int x) {
  queue<int> q;
  q.push(x);
    // 初始化
  for (int i = 1; i <= k; i++) {
    d[i] = INF, fa[i] = 0, st[i] = 0;
  }
    
  d[x] = 0, st[x] = 1;
  while (q.size()) {
    int t = q.front();
    q.pop();
    st[t] = 0;
    for (int i = 1; i < x; i++) {
      if (!e[i][t].size()) continue;
      int w = e[i][t].top().x;
      if (d[i] > d[t] + w) {
        d[i] = d[t] + w;
        fa[i] = t;
        if (!st[i]) {
          st[i] = 1;
          q.push(i);
        }
      }
    }
  }
  vector<int> v;
  int x1 = 1;

  while (x1 != x) {
      // 每个点的最小值,都记录一下,然后放到v里面
    v.ps(e[x1][fa[x1]].top().y);
    in[e[x1][fa[x1]].top().y] = fa[x1];
    x1 = fa[x1];
    // cout << x1 << endl;
  }
    // 最后把v 里面的边都操作一下,因为 v 里面的 都被转移了。
    // 所以需要新加入一些边
  for (auto tt : v) {
    for (int i = 1; i <= k; i++)
      if (in[tt] != i) {
        e[in[tt]][i].push({c[tt][i] - c[tt][in[tt]], tt});
      }
  }
  return d[1];
}
void solve() {
  cin >> n >> k;
  for (int i = 1; i <= k; i++) {
    cin >> cnt[i];
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= k; j++) {
      cin >> c[i][j];
    }
  }
    // 都放到1
  int res = 0;
  for (int i = 1; i <= n; i++) {
    res += c[i][1];
    in[i] = 1;
  }
    // 处理一下 从1 到 其他点的距离
    
  for (int i = 2; i <= k; i++) {
    for (int j = 1; j <= n; j++) {
      e[in[j]][i].push({c[j][i] - c[j][in[j]], j});
    }
      // 先把当前的每个点的 in[i] 到 j 的距离更新好。
 
    int ct = cnt[i];
    while (ct--) {
      for (int k1 = 1; k1 <= i; k1++) {
        for (int k2 = 1; k2 <= i; k2++) {
          while (e[k2][k1].size() > 0 && in[e[k2][k1].top().y] != k2) {
             // 如果当前点的 top.y这个点所在的城市 不等于 k2,说明这个点被更新过,那么说明他被更新过,要pop
            // cout << e[k2][k1].size() << endl;
            e[k2][k1].pop();
          }
        }
      }
      res += spfa(i);
    }
  }
  cout << res << endl;
}

signed main() {
  kd;
  int _;
  _ = 1;
  // cin>>_;
  while (_--) solve();
  return 0;
}

Forest

题意:

给你 n 个点,每个点之间的距离是已知的,问有所有可能的最小生成森林的边权和。

思路:

首先不可能去枚举出每个最小生成森林的情况,所以换种角度去考虑每条边对于最终状态的影响,会分成三种情况, 1. 边权比当前大, 2. 边权比当前小但是没有经过 u v,(u v 是当前这条边的两个端点),3. 边权比当前小同时经过了u v。情况 1 就是 \(2^{x}\):x是大于他的数量,第二种和第三种情况考虑使用容斥,用所有的情况去减去经过了u v的所有情况。

Code

const int N = 18, M = 1 << N, mod = 998244353, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }

int d[N * N][M];  //表示前面 i个点 边集为 j 的方案数

int g[M];  // 表示  边集为 j 的方案数
int pw[N * N];// 二的幂

struct T {
  int x, y, w;
  bool operator<(const T& W) const { return w < W.w; }
} e[N * N];// kur

int add(int x, int y) { return x + y >= mod ? (x + y - mod) : x + y; }



void solve() {
  int n;
  cin >> n;
  int ans = 0;
  pw[0] = 1;// 
  int all = (1 << n) - 1;// 全集,后面需要用到
  for (int i = 1; i <= 100; i++) pw[i] = pw[i - 1] * 2 % mod;
  int m = 0;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
      int c;
      cin >> c;
      if (i < j && c) {
        e[m++] = {i, j, c};
      }
    }
  }
    
  for (int i = 0; i < n; i++) d[0][1 << i] = 1;
    // 每个点只有自己这个点的情况是合法的。
    // g 中每种情况都是1,因为是乘法原理。
    
  for (int i = 0; i < (1 << n); i++) {
    g[i] = 1;
  }
  sort(e, e + m);
  for (int i = 0; i < m; i++) {
      // 枚举边集
    int now = pw[i];// 容斥第一步
    int u = e[i].x, v = e[i].y, w = e[i].w;
      
    for (int S = 0; S < (1 << n); S++) {
        // 如果 s 包含了 u 和 v
      if (!(S >> u & 1) || !(S >> v & 1)) continue;
        // 那么就需要减去 (小于的*其他的补集-->*补集就是找出所有的情况)
      now = add(now, mod - d[i][S] * g[S ^ all] % mod);
    }
      //*pw[m - 1 - i] 是情况1 大于的情况
      // *自己的 边权
      // *now 就是容斥之后
    ans = add(ans, now * w % mod * pw[m - 1 - i] % mod);

    for (int S = 0; S < 1 << n; S++) {
      d[i + 1][S] = d[i][S];
        // 如果包含了u v
      if (!(S >> u & 1) || !(S >> v & 1)) continue;
       // g 包含这条边的两种情况,所以需要×2
      g[S] = g[S] * 2 % mod;
        // 同理
      d[i + 1][S] = d[i + 1][S] * 2 % mod;
      for (int T = (S - 1) & S; T > S - T; T = (T - 1) & S) {
        if ((T >> u & 1) ^ (T >> v & 1)) {
            // 这里 S的一个子集包含了 u或者v的一个,那么可能会从 补集转过来
            // 比如 T是 12 S是123 .那么你之前的再多一个 3 就可以转移过来。
          d[i + 1][S] = (d[i + 1][S] + d[i][T] * d[i][S ^ T] % mod) % mod;
        }
      }
    }
  }
  cout << ans << endl;
}

signed main() {
  kd;
  int _;
  _ = 1;
  // cin>>_;
  while (_--) solve();
  return 0;
}

LTCS

题意:

给出两棵树,求出满足一下三个条件的最大子集大小

  1. 是一个单射,也就是唯一确定,每一个点只能被用到一次。
  2. 对应的权值相等。
  3. 对于每个根节点而言,只能从他们的子节点之间进行匹配

思路:

对于这个题目,如果固定两颗树的两个结点的话,把他们儿子的匹配的值作为边权,对于可以匹配的情况建边,然后进行一次km算法,找出最大的匹配之后然后计算权值就可以。

Code

//看了题解感觉很板子,但是km不会,也不懂怎么把这个诡异的题意转化成人话
const int N = 600, mod = 1e9 + 7, INF = 1e10;
int lowbit(int x) { return x & -x; }
int gcd(int a, int b) { return a % b == 0 ? b : gcd(b, a % b); }

int c1[N], c2[N];
// 点权
int d[N][N], w[N][N];
//每个以 i 和 j 为结点的匹配情况,
// w 表示 每个情况的边权,用于km的匹配

vector<int> v1[N], v2[N];
//边权
// km 究极板子传入边权和边,找出最大的匹配
int km(int n, int m) {
  vector<int> u(n + 1), v(m + 1), p(m + 1), Fa(m + 1);
  for (int i = 1; i <= n; i++) {
    p[0] = i;
    int a = 0;
    vector<int> mn(m + 1, INF);
    vector<bool> used(m + 1, false);
    do {
      used[a] = true;
      int i0 = p[a], del = INF, b = 0;
      for (int j = 1; j <= m; ++j)
        if (!used[j]) {
          int val = w[i0][j] - u[i0] - v[j];
          if (val < mn[j]) mn[j] = val, Fa[j] = a;
          if (mn[j] < del) del = mn[j], b = j;
        }
      for (int j = 0; j <= m; ++j)
        if (used[j])
          u[p[j]] += del, v[j] -= del;
        else
          mn[j] -= del;
      a = b;
    } while (p[a] != 0);
    do {
      int b = Fa[a];
      p[a] = p[b];
      a = b;
    } while (a);
  }
  return -v[0];
}

// dfs2
// 当前的结点。当前的父节点,第二颗的结点,第二课的父节点
void dfs2(int u1, int pr1, int u2, int pr2) {
  for (int j : v2[u2]) {
      //如果可以传下去就传
    if (j != pr2) dfs2(u1, pr1, j, u2);
  }
  for (int j : v1[u1]) {
      // 用儿子更新答案
    if (j != pr1) d[u1][u2] = max(d[u1][u2], d[j][u2]);
  }
  for (int j : v2[u2]) {
      // 同理
    if (j != pr2) d[u1][u2] = max(d[u1][u2], d[u1][j]);
  }
  if (c1[u1] == c2[u2]) {
    int f = (v1[u1].size() <= v2[u2].size());
// f 是为了km算法中把点数多的放到右边
    for (int k1 = 0; k1 < v1[u1].size(); k1++) {
      for (int k2 = 0; k2 < v2[u2].size(); k2++) {
        int j1 = v1[u1][k1], j2 = v2[u2][k2];
        int v = 0;
        if (j1 != pr1 && j2 != pr2) {
          v = -d[j1][j2];
        }
        f ? (w[k1 + 1][k2 + 1] = v) : (w[k2 + 1][k1 + 1]) = v;
      }
    }
    if (f) {
      d[u1][u2] = max(d[u1][u2], 1 - km(v1[u1].size(), v2[u2].size()));
    } else
      d[u1][u2] = max(d[u1][u2], 1 - km(v2[u2].size(), v1[u1].size()));
  }
}

// 
void dfs(int u, int pr) {
  for (int j : v1[u]) {
    if (j == pr) continue;
    dfs(j, u);
  }
  dfs2(u, pr, 1, 0);
}

void solve() {
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> c1[i];
  }
  for (int i = 1; i <= m; i++) {
    cin >> c2[i];
  }
  for (int i = 1; i < n; i++) {
    int a, b;
    cin >> a >> b;
    v1[a].ps(b);
    v1[b].ps(a);
  }
  for (int i = 1; i < m; i++) {
    int a, b;
    cin >> a >> b;
    v2[a].ps(b);
    v2[b].ps(a);
  }
  dfs(1, 0);
  cout << d[1][1] << endl;
}

signed main() {
  kd;
  int _;
  _ = 1;
  // cin>>_;
  while (_--) solve();
  return 0;
}
posted @ 2022-08-29 08:59  黄小轩  阅读(12)  评论(0编辑  收藏  举报