(bitset优化01背包)CF1917F Construct Tree
写在前面
CF1917F Construct Tree 题解
题意
给定\(n\) 条边及其边权,求是否存在一种连接方式将所有边连成一棵树,并且树的带权直径为\(d\)。
思路
首先要知道什么是树的直径。百度一下可以得知,树的直径是树上任意两点距离的最大值。
由于没有规定树的形态,所以连边方式及其多样,所以我们可以建立一个虚根,在虚根上连边,将虚根上连的边的集合合并成若干条链。由于树的形态过于复杂而且数据范围也不允许,所以枚举行不通。
考虑一棵树的形态,很容易得出以下性质:能组成\(d\) 的两条链一定为根上所连的最长的两条链(如果不是,则会存在更大的直径)。根据这个性质我们可以得出一个结论:当\(d\) 小于了最长的两条边之和则肯定不存在某种情况满足题目要求。判掉了这种情况就可以得知次长链一定大于次长边。观察到数据范围,可以用01背包枚举。当枚举到最长边时,如果有其他边组成的链能与最长边组成直径,则该种情况即为答案之一,可以判掉。(为什么不会存在更长的链?因为剩余的每条边可以独立连在根节点上,且其长度一定小于选取的两条链。)
判掉了特殊性接下来该普遍性了。剩下的就两种情况:不存在组合方式和最长的两条链均不短与最长边。参考上面特判的做法,也采用01背包。设计状态,设\(f[j][k]\) ,意为是否存在一种一条链长为\(j\) 一条链长为\(k\)。很容易得出dp方程\(f[j][k]|=f[j-l[i]][k], f[j][k]|=f[j][k-l[i]]\),这样可以保证\(l[i]\) 不会同时出现在两条链上。但是这样的时间复杂度为\(O(nd^2)\)无法通过。由于只存在0/1两种状态,所以可以采用bitset优化掉一维。所以很容易将方程转化为\(f[j]|=f[j]<<l[i], f[j]|=f[k-l[i]]\)。复杂度为\(nd^2/w\),可以卡过。
最后如果存在一种情况第一维和第二维之和为\(d\) 则存在答案,否则不存在。
WARNING:多测不清空,爆零两行泪。还有01背包的滚动数组优化是从大枚举到小的,真是被我的智慧和记忆力还有弱弱的样例雷到了。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2010;
int t;
int n,d,l[maxn];
bool bag[maxn];
bitset<maxn>f[maxn];
inline void clear(){
memset(bag,0,sizeof(bag)),bag[0]=1;
memset(f,0,sizeof(f)),f[0][0]=1;
memset(l,0,sizeof(l));
}
inline void dd(){
clear();
cin>>n>>d;
for(int i=1;i<=n;i++) cin>>l[i];
sort(l+1,l+n+1);
if(d<l[n]+l[n-1]){
cout<<"No"<<'\n';
return;
}
for(int i=1;i<=n;i++){
for(int j=d-1;j>=0;j--)
if(l[i]+j<=d){
bag[l[i]+j]|=bag[j];
if(i==n&&l[i]+j==d&&bag[j]){
cout<<"Yes"<<'\n';
return;
}
}
}
for(int i=1;i<=n;i++)
for(int j=d;j>=0;j--){
f[j]|=f[j]<<l[i];
if(j-l[i]>=0) f[j]|=f[j-l[i]];
}
for(int i=l[n];(i<<1)<=d;i++)
if(f[i][d-i]||f[d-i][i]){
cout<<"Yes"<<'\n';
return;
}
cout<<"No"<<'\n';
}
int main(){
std::ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>t;
while(t--) dd();
return 0;
}

浙公网安备 33010602011771号