题目
T(T<=15)组样例,每次给定n(n<=3e3)个点,
并且给定一个阈值m(m<=1e5),第i个点的权值为ai(ai<=1e5),
对于每个i属于[1,m],问图中是否存在连通块的权值和为i,
如果存在输出1,否则输出0
思路来源
https://www.cnblogs.com/cxhscst2/p/8857376.html
题解
题解就是这么暴力,点分治甚至就是个工具
因为最后答案的连通块一定必经某一个重心,
所以就用点分治统计每个重心能产生出来的答案
bitset<M>sum[i]表示必取i时的连通块哪些和是能凑出来的,
考虑到正常做背包的时候只能往背包里多加一个元素,所以v从u转移过来的时候就必取v即可
v搜完之后回溯到u时,u答案或上v的答案,
因为此时v并没有set(a[v]),所以v更新的答案,实际上是必取u时才能取到的答案,
考虑到最后答案必经枚举的重心u,所以点分治只是用重心的数量降了复杂度
复杂度大概O(n*logn*m/64)
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;
typedef long long ll;
const int N=3e3+10,M=1e5+5;
int head[N],cnt;
struct edge{int v,nex;}e[2*N];
void add(int u,int v){e[++cnt]=edge{v,head[u]};head[u]=cnt;}
bool vis[N];
bitset<M>sum[N],ans;
int n,m,a[N],u,v;
int siz,f[N],sz[N],rt;
void init(int n){
cnt=0;
ans.reset();
for(int i=1;i<=n;++i){
sum[i].reset();
vis[i]=head[i]=0;
}
}
//找下一次的重心rt
void getrt(int u,int fa,bool op){
f[u]=0;sz[u]=1;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(v==fa||vis[v])continue;
getrt(v,u,op);
f[u]=max(f[u],sz[v]);
sz[u]+=sz[v];
}
if(op){
f[u]=max(f[u],siz-sz[u]);
if(f[u]<f[rt])rt=u;
}
}
//计算重心u到子树内每个点的距离
void getdis(int u,int fa){
sum[u]=sum[u]<<a[u];
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(v==fa||vis[v])continue;
sum[v]=sum[u];
getdis(v,u);
sum[u]|=sum[v];
}
ans|=sum[u];
}
//计算以u为根的子树的答案
void cal(int u,int col){
sum[u].reset();
sum[u].set(0);
getdis(u,0);
}
void dfs(int u){
//每次用在u的子树里任取减去在v的子树里的答案
//每次只计算 必经过u的答案
cal(u,0);
vis[u]=1;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(vis[v])continue;
getrt(v,u,0);//获得正确的sz[v]
siz=sz[v];rt=0;
getrt(v,u,1);
dfs(rt);
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
init(n);
for(int i=1;i<n;++i){
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
f[0]=siz=n;rt=0;
getrt(1,0,1),dfs(rt);
for(int i=1;i<=m;++i){
if(ans.test(i))putchar('1');
else putchar('0');
}
puts("");
}
return 0;
}