HDU5909 Tree Cutting【树上连通块DP(点分治+dfs序)】

题目描述:

树,带点权,连通块的权值为块中点权的异或和,求权值= [ 0 , m ) [0,m) [0,m)的连通块的数量。
n ≤ 1000 , m ≤ 2 10 n\le1000,m\le2^{10} n1000,m210

题目分析:

此题可以设 f [ i ] [ j ] f[i][j] f[i][j]表示包含点 i i i的连通块权值为 j j j的数量,合并就用FWT优化,可以做到 O ( n m l o g m ) O(nmlogm) O(nmlogm)
upd at 20.05.27: 用FWT DP的时候每次不用IFWT和FWT,初值直接用定义 ( − 1 ) i & j (-1)^{i\&j} (1)i&j,然后给儿子的FWT每个位置+1,这样DP过程就没有log了,在DP完之后IFWT一下,复杂度是 O ( n m + m l o g m ) O(nm+mlogm) O(nm+mlogm)的。
连通块DP更通用的解法是先点分治,然后求出包含点分中心的连通块的答案。
按照dfs序从后往前进行DP,如果一个点不选,那么它的子树也不能选,即在dfs序上跳到子树外。 f [ i ] [ j ] = f [ i + 1 ] [ j   x o r   a [ i ] ] + f [ i + s i z [ i ] ] [ j ] f[i][j]=f[i+1][j ~xor~a[i]]+f[i+siz[i]][j] f[i][j]=f[i+1][j xor a[i]]+f[i+siz[i]][j]。复杂度 O ( n m l o g n ) O(nmlogn) O(nmlogn)

Code:

#include<bits/stdc++.h>
#define maxn 1005
using namespace std;
const int mod = 1e9+7;
int T,n,k,a[maxn],siz[maxn],dfn[maxn],tim,ln[maxn],f[maxn][1<<10],ans[1<<10];
bool vis[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
void getroot(int u,int ff,int tsz,int &g){
	siz[u]=1; bool flg=1;
	for(int i=fir[u],v;i;i=nxt[i]) if(!vis[v=to[i]]&&v!=ff)
		getroot(v,u,tsz,g),siz[u]+=siz[v],flg&=siz[v]<<1<=tsz;
	if(flg&&(tsz-siz[u])<<1<=tsz) g=u;
}
void dfs(int u,int ff){
	ln[dfn[u]=++tim]=u,siz[u]=1;
	for(int i=fir[u],v;i;i=nxt[i]) if(!vis[v=to[i]]&&v!=ff) dfs(v,u),siz[u]+=siz[v];
}
inline int add(int x,int y){return (x+=y)>=mod?x-mod:x;}
void TDC(int u,int tsz){
	getroot(u,0,tsz,u),vis[u]=1;
	tim=0,dfs(u,0);
	memset(f[siz[u]+1],0,k<<2),f[siz[u]+1][0]=1;
	for(int i=siz[u];i>=2;i--)
		for(int j=0;j<k;j++)
			f[i][j]=add(f[i+1][j^a[ln[i]]],f[i+siz[ln[i]]][j]);
	for(int i=0;i<k;i++) ans[i]=add(ans[i],f[2][i^a[u]]);
	for(int i=fir[u],v;i;i=nxt[i]) if(!vis[v=to[i]]) TDC(v,siz[v]);
}
int main()
{
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		memset(fir,0,(n+1)<<2),tot=0,memset(vis,0,n+1);
		for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),line(x,y),line(y,x);
		memset(ans,0,k<<2),TDC(1,n);
		for(int i=0;i<k;i++) printf("%d%c",ans[i],i==k-1?10:32);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值