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

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

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

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. typedef double db;
  5. const int N=5e4+,inf=0x3f3f3f3f;
  6. int n,k,a[N],hd[N],ne,vis[N],K,siz[N],tot,rt,mx;
  7. ll dp[<<],ans;
  8. struct E {int v,nxt;} e[N<<];
  9. void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
  10. void FWT(ll* a,int n,int f) {
  11. for(int k=; k<n; k<<=)
  12. for(int i=; i<n; i+=k<<)
  13. for(int j=i; j<i+k; ++j)
  14. a[j+k]+=f==?a[j]:-a[j];
  15. }
  16. void getroot(int u,int fa) {
  17. siz[u]=;
  18. int sz=;
  19. for(int i=hd[u]; ~i; i=e[i].nxt) {
  20. int v=e[i].v;
  21. if(vis[v]||v==fa)continue;
  22. getroot(v,u);
  23. siz[u]+=siz[v];
  24. sz=max(sz,siz[v]);
  25. }
  26. sz=max(sz,tot-siz[u]);
  27. if(sz<mx)mx=sz,rt=u;
  28. }
  29. void dfs(int u,int fa,int S) {
  30. dp[S]++;
  31. for(int i=hd[u]; ~i; i=e[i].nxt) {
  32. int v=e[i].v;
  33. if(vis[v]||v==fa)continue;
  34. dfs(v,u,S|a[v]);
  35. }
  36. }
  37. void cal(int u,int ba,int f) {
  38. for(int i=; i<=K; ++i)dp[i]=;
  39. dfs(u,-,a[u]|ba);
  40. FWT(dp,K+,);
  41. for(int i=; i<=K; ++i)dp[i]*=dp[i];
  42. FWT(dp,K+,-);
  43. ans+=dp[K]*f;
  44. }
  45. void solve(int u) {
  46. mx=inf,getroot(u,-),u=rt,cal(u,,),vis[u]=;
  47. for(int i=hd[u]; ~i; i=e[i].nxt) {
  48. int v=e[i].v;
  49. if(!vis[v])tot=siz[v],cal(v,a[u],-),solve(v);
  50. }
  51. }
  52. ll treepartion() {
  53. ans=,tot=n;
  54. solve();
  55. return ans;
  56. }
  57. int main() {
  58. while(scanf("%d%d",&n,&k)==) {
  59. memset(hd,-,sizeof hd),ne=;
  60. memset(vis,,sizeof vis);
  61. K=(<<k)-;
  62. for(int i=; i<=n; ++i)scanf("%d",&a[i]),a[i]=<<(a[i]-);
  63. for(int i=; i<n; ++i) {
  64. int u,v;
  65. scanf("%d%d",&u,&v);
  66. addedge(u,v);
  67. addedge(v,u);
  68. }
  69. printf("%lld\n",treepartion());
  70. }
  71. return ;
  72. }

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

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

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

树剖做法:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. const int N=5e4+,inf=0x3f3f3f3f;
  5. int n,k,a[N],hd[N],ne,S,fa[N],son[N],siz[N],tot;
  6. ll dp[][<<],b[<<],A[N],B[N],ans;
  7. struct E {int v,nxt;} e[N<<];
  8. void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
  9. void FWT(ll* a,int n,int f) {
  10. for(int k=; k<n; k<<=)
  11. for(int i=; i<n; i+=k<<)
  12. for(int j=i; j<i+k; ++j) {
  13. ll x=a[j],y=a[j+k];
  14. a[j+k]=f==?y+x:y-x;
  15. }
  16. }
  17. void mul(ll* a,ll* b,ll* c,int n) {
  18. for(int i=; i<n; ++i)A[i]=a[i],B[i]=b[i];
  19. FWT(A,n,),FWT(B,n,);
  20. for(int i=; i<n; ++i)c[i]=A[i]*B[i];
  21. FWT(c,n,-);
  22. }
  23. int newnode() {
  24. int u=tot++;
  25. for(int i=; i<(<<k); ++i)dp[u][i]=;
  26. return u;
  27. }
  28. void dfs1(int u,int f) {
  29. fa[u]=f,son[u]=,siz[u]=;
  30. for(int i=hd[u]; ~i; i=e[i].nxt) {
  31. int v=e[i].v;
  32. if(v==fa[u])continue;
  33. dfs1(v,u),siz[u]+=siz[v];
  34. if(siz[v]>siz[son[u]])son[u]=v;
  35. }
  36. }
  37. void dfs2(int u,int w) {
  38. if(son[u])dfs2(son[u],w);
  39. for(int i=; i<(<<k); ++i)b[i]=;
  40. b[<<a[u]]=;
  41. if((<<a[u]==S))ans++;
  42. for(int i=; i<(<<k); ++i)if((i|(<<a[u]))==S)ans+=dp[w][i]*;
  43. for(int i=; i<(<<k); ++i)b[i|(<<a[u])]+=dp[w][i];
  44. for(int i=; i<(<<k); ++i)dp[w][i]=b[i];
  45. for(int i=hd[u]; ~i; i=e[i].nxt) {
  46. int v=e[i].v;
  47. if(v==fa[u]||v==son[u])continue;
  48. int wv=newnode();
  49. dfs2(v,wv),tot--;
  50. mul(dp[w],dp[wv],b,<<k);
  51. ans+=b[S]*;
  52. for(int i=; i<(<<k); ++i)dp[w][i|(<<a[u])]+=dp[wv][i];
  53. }
  54. }
  55. int main() {
  56. while(scanf("%d%d",&n,&k)==) {
  57. memset(hd,-,sizeof hd),ne=;
  58. S=(<<k)-;
  59. for(int i=; i<=n; ++i)scanf("%d",&a[i]),a[i]--;
  60. for(int i=; i<n; ++i) {
  61. int u,v;
  62. scanf("%d%d",&u,&v);
  63. addedge(u,v);
  64. addedge(v,u);
  65. }
  66. tot=ans=;
  67. dfs1(,-),dfs2(,newnode());
  68. printf("%lld\n",ans);
  69. }
  70. return ;
  71. }

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

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

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. const int N=5e4+,inf=0x3f3f3f3f;
  5. int n,k,a[N],siz[N],hd[N],ne,ppc[<<];
  6. ll ans;
  7. struct E {int v,nxt;} e[N<<];
  8. void addedge(int u,int v) {e[ne]= {v,hd[u]},hd[u]=ne++;}
  9. void dfs(int u,int fa,int f) {
  10. for(int i=hd[u]; ~i; i=e[i].nxt)if(e[i].v!=fa)dfs(e[i].v,u,f);
  11. if(!siz[u])return;
  12. ans+=f;
  13. for(int i=hd[u]; ~i; i=e[i].nxt)if(e[i].v!=fa) {
  14. int v=e[i].v;
  15. ans+=(ll)siz[v]*siz[u]**f;
  16. siz[u]+=siz[v];
  17. }
  18. }
  19. ll solve() {
  20. ans=;
  21. for(int S=(<<k)-; S; --S) {
  22. int f=(k-ppc[S])&?-:;
  23. for(int i=; i<=n; ++i)siz[i]=S>>a[i]&;
  24. dfs(,-,f);
  25. }
  26. return ans;
  27. }
  28. int main() {
  29. ppc[]=;
  30. for(int i=; i<(<<); ++i)ppc[i]=ppc[i>>]+(i&);
  31. while(scanf("%d%d",&n,&k)==) {
  32. memset(hd,-,sizeof hd),ne=;
  33. for(int i=; i<=n; ++i)scanf("%d",&a[i]),a[i]--;
  34. for(int i=; i<n; ++i) {
  35. int u,v;
  36. scanf("%d%d",&u,&v);
  37. addedge(u,v);
  38. addedge(v,u);
  39. }
  40. printf("%lld\n",solve());
  41. }
  42. return ;
  43. }

这种方法的复杂度为什么会比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. openstack ocata版(脚本)控制节点安装

    一.初始化环境: 1.更换yum源: yum install -y wget mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS- ...

  2. python的PIL模块安装

    一.Centos安装PIL #尤其重要,否则会报错 yum install python-devel yum install libjpeg libjpeg-devel zlib zlib-devel ...

  3. Linux CentOS7安装Mysql5.7

    一.下载mysql mkdir /home/install #创建install目录 在/home/install目录下下载mysql5.7 wget https://cdn.mysql.com//D ...

  4. 设计模式—迭代器Iterator模式

    什么是迭代器模式? 让用户通过特定的接口访问容器的数据,不需要了解容器内部的数据结构. 首先我们先模仿集合中ArrayList和LinkedList的实现.一个是基于数组的实现.一个是基于链表的实现, ...

  5. jquery click()方法模拟点击事件对a标签不生效

    if(e.keyCode == 13) { $items.eq(index).click(); return; } 搜索框下拉列表模拟点击时间,使用上述代码不能触发链接跳转 1,页面使用了bootst ...

  6. linux下常用FTP命令 1. 连接ftp服务器[转]

    1. 连接ftp服务器 格式:ftp [hostname| ip-address]a)在linux命令行下输入: ftp 192.168.1.1 b)服务器询问你用户名和密码,分别输入用户名和相应密码 ...

  7. how to use composer in fiddler

    https://www.cnblogs.com/youxin/p/3570310.html http://docs.telerik.com/fiddler/generate-traffic/tasks ...

  8. 使用UNIDAC连接oracle时的参数设置

    在uniconnection1里设置: server项位hostip:port:sid,如10.53.x.XX:1521:or10g 然后在Options里设置: charset:utf8 direc ...

  9. spark学习3(sqoop1.4.6安装)

    sqoop目前有两个版本sqoop1和sqoop2,这里安装的是sqoop1版本 1)将软件上传到spark1节点 2)修改权限 [root@spark1 sqoop]# chmod u+x sqoo ...

  10. MapReduce-二进制输入

    Hadoop的MapReduce不只是可以处理文本信息,它还可以处理二进制格式的数据1. 关于SequenceFileInputFormat类Hadoop的顺序文件格式存储二进制的键/值对的序列.由于 ...