【CF1917F】Construct Tree
题目
题目链接:https://codeforces.com/contest/1917/problem/F
给出 \(n\) 条边的边权,询问是否可以构造出一棵树,使得所有边都被用上恰好一次且直径为 \(d\)。
\(n,d\leq 2000\)。
思路
首先肯定是找出一条长度为 \(d\) 的链,然后判断可不可以把剩下的所有边都挂在这条链的带权重心上。
如果最长的两条边的和已经超过 \(d\) 了,那么无解。
否则,考虑最长的边是否在这个长度为 \(d\) 的链上:
- 如果在,那就把最长的边放在链的最前端,然后将剩余的边都挂在链的第二个节点上。显然这样不会产生长度超过 \(d\) 的链。
- 如果不在,那么需要找到这条链上是否存在一个点,使得这个点切开后两条新的链长度都不小于最长的边的长度。
设 \(f[i][j][k]\) 表示是否存在一种情况,满足前 \(i\) 个边选择了若干个,使得第一条链长度为 \(j\),第二条链长度为 \(k\)。然后直接上 01 背包即可。压掉第一维然后用 bitset 可以优化到 \(O(\frac{nd^2}{64})\)。
时间复杂度 \(O(\frac{nd^2}{64})\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2010;
int T,n,d,a[N];
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&d);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
if (a[n]+a[n-1]>d) { printf("No\n"); continue; }
bitset<N> f[d+1]; f[0][0]=1;
for (int i=1;i<n;i++)
for (int j=d;j>=0;j--)
{
f[j]|=(f[j]<<a[i]);
if (j>=a[i]) f[j]|=f[j-a[i]];
}
bool flag=f[d-a[n]][0];
for (int i=a[n];i<=d-a[n];i++)
flag|=f[i][d-i];
printf("%s\n",(flag ? "Yes" : "No"));
}
return 0;
}