CF1917F Construct Tree

Link:http://codeforces.com/problemset/problem/1917/F

知识点:背包,构造

简述

\(T\) 组数据,每组数据给定参数 \(d\) 与一长度为 \(n\) 的数列 \(l\),仅需判断是否可以构造出一棵树,满足:

  • 树的所有边长与数列元素一一对应。
  • 树的直径为 \(d\)

\(1\le T\le 250\)\(2\le n\le 2000\)\(1\le l_i\le d\le 2000\)\(\sum n\le 2000\)
2S,256MB。

分析

先和 dztle 讨论出一堆结论:

首先若存在 \(l_{i} + l_{j}>d\) 显然无解。

否则若仅存在唯一的 \(l_i>\frac{d}{2}\),则这条边一定在直径上,则此时最可行的构造是用其他边构造出一条长为 \(d-l_i\) 的链与 \(l_i\) 相连构成直径,然后将其他边直接接到 \(l_i\) 靠近直径中点的端点。直接背包判断即可。

否则考虑先构造出长为 \(d\) 的直径,考虑其他边应该接到哪里:

  • 发现所有边直接连到距离直径的中点最近的点 \(m\)上是最可行的。
  • \(m\) 与较近的直径端点距离为 \(d'\)(显然有 \(d'\le \frac{d}{2}\))。则若所有边的长度不大于 \(d'\) 说明构造合法。
  • 为什么合法?考虑一条边 \(l_i(l_i\le d')\) 位于该构造中的什么位置:
    • 边在 \(m\) 与较近的直径端点的链上,则显然有 \(l_i\le d'\le \frac{d}{2}\)
    • 边在 \(m\) 与较远的直径端点的链上,则有 \(l_i<d'<\frac{d}{2}, d'+l_i\le d\),则直径合法。
    • 边不在直径上且与 \(m\) 相连,则有 \(d'+l_i\le d, d - d' + l_i\le d - d' + d' = d\),则直径合法。
  • 由上述讨论可知当 \(\forall 1\le i\le n, l_i\le d'\) 时在这棵树上所有边都有自己要做的事,所以构造合法。否则必然出现与直径为 \(d\) 冲突的情况。

于是同样考虑背包,首先想到设 \(f_i\) 表示构造出长度为 \(i\) 的链时,最靠近 \(\frac{d}{2}\) 的位置与较近的端点的距离。初始化 \(f_0=0, \forall 1\le i\le d, f_i = -\infin\),转移时枚举当前要加入的边 \(l_i\),再枚举加入边后的链长 \(j\),则有转移:

\[\begin{cases} f_j\leftarrow f_{j-l_i} + l_i &(f_{j - l_i} + l_i\le \frac{d}{2})\\ f_j\leftarrow f_{j - l_i} &\text{otherwise} \end{cases}\]

然后发现这个状态有点问题,有可能长度为 \(l-a_i\) 的链存在多种构造使得最靠近 \(\frac{d}{2}\) 的位置与较近的端点的距离不同,为了考虑周全应当把这些情况都记录下来。于是考虑修改状态将 \(f\) 设为 bitset,表示构造出长度为 \(i\) 的链时,最靠近 \(\frac{d}{2}\) 的位置与较近的端点的距离为某个值是否可行。初始化 \(f_{0, 0} = \text{true}\) 同样修改上述两种转移的形式:

  • \(f_j\leftarrow f_{j-l_i} + l_i\),等价于 \(f_j\) 按位或上 \(f_{j-l_i}\) 左移 \(l_i\) 位的前 \(\frac{d}{2}\) 位。
  • \(f_j\leftarrow f_{j - l_i}\),等价于 \(f_j\) 直接或上 \(f_{j-l_i}\)

最后检查 \(f_d\) 最右侧的 1 的位置是否大于最长的边即可。

总时间复杂度大概是 \(O\left( \frac{n^2d}{w} \right)\)\(w = 64\),反正能过吸吸

代码

//知识点:背包
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2010;
//=============================================================
int n, d, ans, a[kN];
bool g[kN];
std::bitset <kN> f[kN], h;
//=============================================================
bool DP1() {
  g[0] = 1;
  for (int i = 1; i <= d; ++ i) g[i] = 0;
  for (int i = 1; i < n; ++ i) {
    for (int j = d - a[n]; j >= a[i]; -- j) {
      g[j] |= g[j - a[i]];
    }
  }
  return g[d - a[n]];
}
bool DP2() {
  h.reset();
  for (int i = 0; i <= d / 2; ++ i) h.set(i);
  for (int i = 0; i <= d; ++ i) f[i].reset();
  f[0].set(0);
  
  for (int i = 1; i <= n; ++ i) {
    for (int j = d; j >= a[i]; -- j) {
      f[j] |= f[j - a[i]] | ((f[j - a[i]] << a[i]) & h);
    }
  }
  return f[d]._Find_next(a[n] - 1) != f[d].size();
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(false);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> d;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    std::sort(a + 1, a + n + 1);
    if (a[n] + a[n - 1] > d) {
      ans = 0;
    } else if (a[n] > d / 2) {
      ans = DP1();
    } else {
      ans = DP2();
    }
    std::cout << (ans ? "Yes\n" : "No\n");
  }
  return 0;
}
posted @ 2024-01-26 10:28  Luckyblock  阅读(9)  评论(0编辑  收藏  举报