Description
给你一个数组 l1,l2,….ln 和一个数字 d。问你是否能够构造一棵树满足以下条件:
- 这棵树有 n+1 个点。
- 第 i 条边的长度是 li。
- 树的直径是 d。
只需要判断是否有解即可。
2≤n≤2000,1≤d≤2000,1≤li≤d。
Solution
先把 l 从大到小排序。
容易发现把直径拉出来后,其他不在直径上的边 lk 挂在直径的点上要满足 lk≤min{L,d−L},其中 L 和 d−L 分别是直径上挂的点左右的长度和。
所以肯定是把不在直径上的边以类似菊花图的形式尽可能挂在中间,使得所有 lk≤min{L,d−L}。
如果 ln>d 显然无解。
如果 ln>⌊2d⌋,则它一定不能是挂着的边,也就是一定在直径上,这时候只要判断 l1,l2….ln−1 都 ≤d−ln 并且能够选出某些数和为 d−ln。
如果 ln≤⌊2d⌋,并且 ln 在直径上,则其他点一定能全挂上去,所以只要判断能否有若干个和为 d−ln 即可。如果 ln 不在直径上,那么就需要保证 min{L,d−L}≥ln,所以直接设 fi,j 表示左边和为 i,右边和为 j 是否可行即可求出。
时间复杂度:O(nd2),过不了。
但是注意到 fi,j 的转移只有 0/1,所以可以 bitset 优化至 O(ωnd2)。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <bits/stdc++.h>
const int kMaxN = 2e3 + 5;
int n, d; int a[kMaxN]; std::bitset<kMaxN> f[kMaxN];
void dickdreamer() { std::cin >> n >> d; for (int i = 1; i <= n; ++i) std::cin >> a[i]; std::sort(a + 1, a + 1 + n); if (a[n] > d / 2) { if (a[n - 1] > d - a[n]) return void(std::cout << "No\n"); f[0].reset(); f[0][0] = 1; for (int i = 1; i < n; ++i) f[0] |= (f[0] << a[i]); std::cout << (f[0][d - a[n]] ? "Yes\n" : "No\n"); } else { for (int i = 0; i <= d; ++i) f[i].reset(); f[0][0] = 1; for (int i = 1; i < n; ++i) { for (int j = d; ~j; --j) { f[j] |= (f[j] << a[i]); if (j >= a[i]) f[j] |= f[j - a[i]]; } } bool ans = f[0][d - a[n]]; for (int i = a[n]; i <= d - a[n]; ++i) ans |= f[i][d - i]; std::cout << (ans ? "Yes\n" : "No\n"); } }
int32_t main() { #ifdef ORZXKR freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0); int T = 1; std::cin >> T; while (T--) dickdreamer(); return 0; }
|