题意:一棵树上有n(n<=50000)个结点,结点有k(k<=10)种颜色,问树上总共有多少条包含所有颜色的路径。

我最初的想法是树形状压dp,设dp[u][S]为以结点u为根的包含颜色集合为S的路径条数,然后FWT(应该叫FMT?)搞一下就行了,复杂度$O(nk2^k)$。奈何内存太大,妥妥地MLE...

看到网上大部分的解法都是点分治,我不禁联想到之前学过的树上任意两点距离的求法(点分治+FFT),心想,这道题用点分治+FWT是不是也能过?于是比着葫芦画瓢写出了这样一段又臭又长的代码:

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int N=5e4+,inf=0x3f3f3f3f;
int n,k,a[N],hd[N],ne,vis[N],K,siz[N],tot,rt,mx;
ll dp[<<],ans;
struct E {int v,nxt;} e[N<<];
void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
void FWT(ll* a,int n,int f) {
for(int k=; k<n; k<<=)
for(int i=; i<n; i+=k<<)
for(int j=i; j<i+k; ++j)
a[j+k]+=f==?a[j]:-a[j];
}
void getroot(int u,int fa) {
siz[u]=;
int sz=;
for(int i=hd[u]; ~i; i=e[i].nxt) {
int v=e[i].v;
if(vis[v]||v==fa)continue;
getroot(v,u);
siz[u]+=siz[v];
sz=max(sz,siz[v]);
}
sz=max(sz,tot-siz[u]);
if(sz<mx)mx=sz,rt=u;
}
void dfs(int u,int fa,int S) {
dp[S]++;
for(int i=hd[u]; ~i; i=e[i].nxt) {
int v=e[i].v;
if(vis[v]||v==fa)continue;
dfs(v,u,S|a[v]);
}
}
void cal(int u,int ba,int f) {
for(int i=; i<=K; ++i)dp[i]=;
dfs(u,-,a[u]|ba);
FWT(dp,K+,);
for(int i=; i<=K; ++i)dp[i]*=dp[i];
FWT(dp,K+,-);
ans+=dp[K]*f;
}
void solve(int u) {
mx=inf,getroot(u,-),u=rt,cal(u,,),vis[u]=;
for(int i=hd[u]; ~i; i=e[i].nxt) {
int v=e[i].v;
if(!vis[v])tot=siz[v],cal(v,a[u],-),solve(v);
}
}
ll treepartion() {
ans=,tot=n;
solve();
return ans;
}
int main() {
while(scanf("%d%d",&n,&k)==) {
memset(hd,-,sizeof hd),ne=;
memset(vis,,sizeof vis);
K=(<<k)-;
for(int i=; i<=n; ++i)scanf("%d",&a[i]),a[i]=<<(a[i]-);
for(int i=; i<n; ++i) {
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
printf("%lld\n",treepartion());
}
return ;
}

虽然成功地AC了,但是仔细一想:不对啊,这道题FWT的复杂度和子树的大小不是线性相关的啊!所以这样一来,总的复杂度成了$O(nk2^klogn)$,反而增大了。

也就是说,这道题用点分治的作用仅仅是减少了内存的开销,复杂度非但没有减少,反而还多了个logn!

当然除了点分治,这道题还有其他的优化方法,比如sclbgw7大佬利用树链剖分的思想将内存优化到了$O(2^klogn)$,时间复杂度仍为$O(nk2^k)$。

树剖做法:

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+,inf=0x3f3f3f3f;
int n,k,a[N],hd[N],ne,S,fa[N],son[N],siz[N],tot;
ll dp[][<<],b[<<],A[N],B[N],ans;
struct E {int v,nxt;} e[N<<];
void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
void FWT(ll* a,int n,int f) {
for(int k=; k<n; k<<=)
for(int i=; i<n; i+=k<<)
for(int j=i; j<i+k; ++j) {
ll x=a[j],y=a[j+k];
a[j+k]=f==?y+x:y-x;
}
}
void mul(ll* a,ll* b,ll* c,int n) {
for(int i=; i<n; ++i)A[i]=a[i],B[i]=b[i];
FWT(A,n,),FWT(B,n,);
for(int i=; i<n; ++i)c[i]=A[i]*B[i];
FWT(c,n,-);
}
int newnode() {
int u=tot++;
for(int i=; i<(<<k); ++i)dp[u][i]=;
return u;
}
void dfs1(int u,int f) {
fa[u]=f,son[u]=,siz[u]=;
for(int i=hd[u]; ~i; i=e[i].nxt) {
int v=e[i].v;
if(v==fa[u])continue;
dfs1(v,u),siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v;
}
}
void dfs2(int u,int w) {
if(son[u])dfs2(son[u],w);
for(int i=; i<(<<k); ++i)b[i]=;
b[<<a[u]]=;
if((<<a[u]==S))ans++;
for(int i=; i<(<<k); ++i)if((i|(<<a[u]))==S)ans+=dp[w][i]*;
for(int i=; i<(<<k); ++i)b[i|(<<a[u])]+=dp[w][i];
for(int i=; i<(<<k); ++i)dp[w][i]=b[i];
for(int i=hd[u]; ~i; i=e[i].nxt) {
int v=e[i].v;
if(v==fa[u]||v==son[u])continue;
int wv=newnode();
dfs2(v,wv),tot--;
mul(dp[w],dp[wv],b,<<k);
ans+=b[S]*;
for(int i=; i<(<<k); ++i)dp[w][i|(<<a[u])]+=dp[wv][i];
}
}
int main() {
while(scanf("%d%d",&n,&k)==) {
memset(hd,-,sizeof hd),ne=;
S=(<<k)-;
for(int i=; i<=n; ++i)scanf("%d",&a[i]),a[i]--;
for(int i=; i<n; ++i) {
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
tot=ans=;
dfs1(,-),dfs2(,newnode());
printf("%lld\n",ans);
}
return ;
}

还有Menhera大佬利用基的FMT性质将时间复杂度优化到$O(n2^k)$的做法,看样子有点像是容斥。我个人更倾向于这一种,于是在这个思想的基础上进一步地分析:

题目要求的是包含所有颜色的路径条数。如果包含某个元素集合的路径不太好求,那么不包含某个元素集合的呢?只要把属于这个集合的结点都染成白色,不属于的都染成黑色,则问题就转化成了求一棵树上包含的所有点都是黑色的路径条数,直接dp求一下就行了。于是我们可以利用容斥原理,用所有的路径数减去不包含1个元素集合的路径数,再加上不包含2个元素集合的路径数,再减去不包含3个...就得到了答案。

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+,inf=0x3f3f3f3f;
int n,k,a[N],siz[N],hd[N],ne,ppc[<<];
ll ans;
struct E {int v,nxt;} e[N<<];
void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
void dfs(int u,int fa,int f) {
for(int i=hd[u]; ~i; i=e[i].nxt)if(e[i].v!=fa)dfs(e[i].v,u,f);
if(!siz[u])return;
ans+=f;
for(int i=hd[u]; ~i; i=e[i].nxt)if(e[i].v!=fa) {
int v=e[i].v;
ans+=(ll)siz[v]*siz[u]**f;
siz[u]+=siz[v];
}
}
ll solve() {
ans=;
for(int S=(<<k)-; S; --S) {
int f=(k-ppc[S])&?-:;
for(int i=; i<=n; ++i)siz[i]=S>>a[i]&;
dfs(,-,f);
}
return ans;
}
int main() {
ppc[]=;
for(int i=; i<(<<); ++i)ppc[i]=ppc[i>>]+(i&);
while(scanf("%d%d",&n,&k)==) {
memset(hd,-,sizeof hd),ne=;
for(int i=; i<=n; ++i)scanf("%d",&a[i]),a[i]--;
for(int i=; i<n; ++i) {
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
printf("%lld\n",solve());
}
return ;
}

这种方法的复杂度为什么会比FWT少了k呢?这个k哪里去了呢?我想大概是在FWT的过程中把所有集合的dp值都求出来了,而我们只需要求全集的dp值,因此多做了许多无用功。

(ps:由于题目数据的限制,用map优化的点分治可能会更快一些)

HDU - 5977 Garden of Eden (树形dp+容斥)的更多相关文章

  1. HDU 5977 Garden of Eden (树形dp+快速沃尔什变换FWT)

    CGZ大佬提醒我,我要是再不更博客可就连一月一更的频率也没有了... emmm,正好做了一道有点意思的题,就拿出来充数吧=.= 题意 一棵树,有 $ n (n\leq50000) $ 个节点,每个点都 ...

  2. HDU 5977 Garden of Eden(点分治求点对路径颜色数为K)

    Problem Description When God made the first man, he put him on a beautiful garden, the Garden of Ede ...

  3. P5405-[CTS2019]氪金手游【树形dp,容斥,数学期望】

    前言 话说在\(Loj\)下了个数据发现这题的名字叫\(fgo\) 正题 题目链接:https://www.luogu.com.cn/problem/P5405 题目大意 \(n\)张卡的权值为\(1 ...

  4. bzoj 4455 [Zjoi2016]小星星 树形dp&容斥

    4455: [Zjoi2016]小星星 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 643  Solved: 391[Submit][Status] ...

  5. [JSOI2019]神经网络(树形DP+容斥+生成函数)

    首先可以把题目转化一下:把树拆成若干条链,每条链的颜色为其所在的树的颜色,然后排放所有的链成环,求使得相邻位置颜色不同的排列方案数. 然后本题分为两个部分:将一棵树分为1~n条不相交的链的方案数:将这 ...

  6. [USACO12FEB] 附近的牛 Nearby Cows - 树形dp,容斥

    给你一棵 \(n\) 个点的树,点带权,对于每个节点求出距离它不超过 \(k\) 的所有节点权值和 \(m_i\) 随便定一个根,设\(f[i][j]\)表示只考虑子树,距离为\(j\)的权值和,\( ...

  7. hdu 5977 Garden of Eden(点分治+状压)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5977 题解:这题一看就知道是状压dp然后看了一下很像是点分治(有点明显)然后就是简单的点分治+状压dp ...

  8. HDU 5977 Garden of Eden

    题解: 路径统计比较容易想到点分治和dp dp的话是f[i][j]表示以i为根,取了i,颜色数状态为j的方案数 但是转移这里如果暴力转移就是$(2^k)^2$了 于是用FWT优化集合或 另外http: ...

  9. HDU 5977 Garden of Eden (树分治+状态压缩)

    题意:给一棵节点数为n,节点种类为k的无根树,问其中有多少种不同的简单路径,可以满足路径上经过所有k种类型的点? 析:对于路径,就是两类,第一种情况,就是跨过根结点,第二种是不跨过根结点,分别讨论就好 ...

随机推荐

  1. XSS - html过滤

    JS 根据白名单过滤HTML http://jsxss.com/zh/index.html   方案一: java的一个方案, 可以参考:  http://winnie825.iteye.com/bl ...

  2. 预防SQL注入攻击

    /** * 预防SQL注入攻击 * @param string $value * @return string */ function check_input($value) { // 去除斜杠 if ...

  3. g高分屏DataGrid里面checkbox不显示的解决办法

  4. HP小型机维护

    (一)文件系统维护 . 监控文件系统的使用 # bdf . 监控文件目录的使用 # du -sk /myfs2/* (二)网络系统维护 1. 相关配置文件 1). 主机名定义文件:/etc/hosts ...

  5. Linux挂载Windows共享目录

    在windows中设置共享目录并添加权限用户 把Window系统的文件共享挂载到linux centos 目录下的方法步骤: 1.先在windows下面共享需要挂载的目录. 2.确保linux与win ...

  6. samba Doc

    Samba-HOWTO-Collection中文翻译版(2.20) 2013年08月23日 ⁄ 综合 ⁄ 共 19460字 ⁄ 字号 小 中 大 ⁄ 评论关闭 Samba计划文档 (初稿) Samba ...

  7. Linux 内核是如何构建

    https://github.com/MintCN/linux-insides-zh 介绍 我不会告诉你怎么在自己的电脑上去构建.安装一个定制化的 Linux 内核,这样的资料太多了,它们会对你有帮助 ...

  8. Flume具体应用(多案例)

    日志采集 对于flume的原理其实很容易理解,我们更应该掌握flume的具体使用方法,flume提供了大量内置的Source.Channel和Sink类型.而且不同类型的Source.Channel和 ...

  9. C#使用Sockets操作FTP

    http://blog.csdn.net/foart/article/details/6824551 using System; using System.Collections; using Sys ...

  10. 用find命令查找最近修改过的文件

    Linux的终端上,没有windows的搜索那样好用的图形界面工具,但find命令确是很强大的. 比如按名字查找一个文件,可以用 find / -name targetfilename . 唉,如果只 ...