CGZ大佬提醒我,我要是再不更博客可就连一月一更的频率也没有了。。。

emmm,正好做了一道有点意思的题,就拿出来充数吧=。=

题意

一棵树,有 $ n (n\leq50000) $ 个节点,每个点都有一个颜色,共有 $ k(k\leq10) $ 种颜色,问有多少条路径可以遍历到所有 $ k $ 种颜色?(一条路径交换起点终点就算两条哦)

做法

事实证明,连我都能不看题解想出来的题果然都是水题qwq

我是从CJ的xzyxzy大佬上的博客上看到这道题的,所以就理所当然用FWT做了...然后才发现网上的题解都是点分治...Menhera大佬提供了一个更优的做法,不过我是真的看不懂...放在最后讲一下(在代码后面)。

这道题一眼就是树形dp,而且k特别小,貌似可以状压?

用二进制数 $ S $ 表示一条路径上的颜色种类,用 $ dp[i][S] $ 表示当前节点 $ i $ 到它下面的叶子节点中,颜色状态为 $ S $ 的路径的数量。很显然 $ S=2^k-1 $ 的路径就是我们要找的路径,我们的目标就是求出这样的路径的数量ヾ(゚∀゚ゞ)!

求出 $ dp[i][S] $ 是很容易的,只需要遍历一遍就行了。然而,有的路径的两端会在 $ i $ 的两个子树中而横跨 $ i $ 这个结点,这样的路径怎么统计呢?总不能一个个枚举吧。。这就该FWT上场了!把要统计的两个子树的 $ dp[x][S] $ 做or卷积,然后把 $ S=111...1 $ 的路径条数累加进答案就可以啦!注意,FWT是在辅助数组中进行的,不应该改变原数组。在一遍FWT后,将第二个儿子的路径数累加进第一个儿子的路径数,接着将第三个儿子又与第一个儿子做FWT,以此类推,即可求出所有横跨各个子树的路径了。这样做时间复杂度是 $ O(n2^kk) $ ,而这道题的时限是5s,所以还是可以轻松跑过的。

emmm...(在打了一会代码之后)

等等,哪里有点问题...树上有50000个点,每个点需要大小为1024的数组来存储状态,我需要开整整195MB的数组?!

经过一番思考...我终于发现了这样的方法:先一遍dfs求出每个节点的重儿子,dp时优先递归重儿子,然后递归别的儿子,一遍FWT求出答案后再将两个儿子的状态合并,回收轻儿子的空间,在接着递归别的儿子。这样,可以证明在某一时刻最多同时存在 $ log_2n $ 个儿子的状态(与树剖的证明相似),所以空间就只需要开一点点啦~

代码:(有很详细的注释哦qwq不会的可以看一下代码)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
#define R register
using namespace std;
typedef long long LL;
const int MAXN=50100;
const int MAXM=1100;
int he[MAXN],col[MAXN];
int siz[MAXN],son[MAXN];
int dp[100][MAXM];
int n,k,cnt,len;
LL ans;//注意ans是有可能爆int的 template<class T>int read(T &x)//这是zyf看了会沉默的可以判EOF的快读
{
x=0;int ff=0;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!=EOF){ff|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=ff?-x:x;
if(ch==EOF)return EOF;
return 1;
} class FWT
{
private:
LL tem1[MAXM],tem2[MAXM]; void fwt_or(LL *a,int f)//普通的FWT,但是zyf说or和and卷积都应该叫做快速莫比乌斯变换(FMT)?
{
for(R int i=1;i<len;i<<=1)
for(R int j=0;j<len;j+=(i<<1))
for(R int k=0;k<i;++k)
if(f==1)a[j+k+i]+=a[j+k];
else a[j+k+i]-=a[j+k];
} public:
int fwt(int *a,int *b)
{
for(R int i=0;i<len;++i)tem1[i]=a[i];
for(R int i=0;i<len;++i)tem2[i]=b[i];
fwt_or(tem1,1);
fwt_or(tem2,1);
for(R int i=0;i<len;++i)tem1[i]*=tem2[i];
fwt_or(tem1,-1);
return (int)tem1[len-1];
}
}fwt; struct edge
{
int to,next;
}ed[MAXN<<1]; void added(int x,int y)//加边,常规操作
{
ed[++cnt].to=y;
ed[cnt].next=he[x];
he[x]=cnt;
} void dfs_pre(int x,int fa)//求重儿子
{
siz[x]=1,son[x]=0;
for(int i=he[x];i;i=ed[i].next)
{
int to=ed[i].to;
if(to==fa)continue;
dfs_pre(to,x);
siz[x]+=siz[to];
if(siz[to]>siz[son[x]])son[x]=to;
}
} stack<int>stk;//用于回收空间
int dfs(int x,int fa)
{
int num,bt=1<<col[x];
if(son[x])num=dfs(son[x],x);//继承重儿子的空间
else//是叶节点
{
if(!stk.empty())//使用回收后的空间
num=stk.top(),stk.pop();
else num=++cnt;//使用新空间
dp[num][bt]=1;
return num;//上传空间给父亲
}
for(R int i=1;i<len;++i)//根据重儿子的状态推出自己的状态
if(dp[num][i])
if(!(i&bt))
dp[num][i|bt]+=dp[num][i],dp[num][i]=0;
ans+=dp[num][len-1];
dp[num][bt]+=1;
for(int j=he[x];j;j=ed[j].next)//枚举轻儿子们
{
int to=ed[j].to;
if(to==fa||to==son[x])continue;
int tnum=dfs(to,x);//得到轻儿子的状态
ans+=fwt.fwt(dp[num],dp[tnum]);//FWT并累加答案
for(R int i=1;i<len;++i)//将这个轻儿子的状态合并至自己的状态
if(dp[tnum][i])
if(!(i&bt))
dp[num][i|bt]+=dp[tnum][i],dp[tnum][i]=0;
else dp[num][i]+=dp[tnum][i],dp[tnum][i]=0;
stk.push(tnum);//空间回收
}
return num;//上传空间给父亲
} int main()
{
while(read(n)!=EOF)
{
memset(he,0,sizeof(he));
memset(dp,0,sizeof(dp));
while(!stk.empty())stk.pop();//各种清零不要忘
read(k);
len=1<<k;
for(R int i=1;i<=n;++i)
read(col[i]),--col[i];
int t1,t2;
cnt=0,ans=0;
for(R int i=1;i<n;++i)//加边
{
read(t1),read(t2);
added(t1,t2);
added(t2,t1);
}
if(k==1)//特判就好了,可以省很多事
{
ans=1ll*n*(n-1);
ans+=n;
printf("%lld\n",ans);
continue;
}
dfs_pre(1,0);
cnt=0;//这里cnt被用于标记当前使用的空间
dfs(1,0);
ans<<=1;//别忘了交换起点终点后的路径算的不同路径
printf("%lld\n",ans);
}
return 0;
}

什么?优化?

我一个在明德的好朋友Menhera酱发现可以把复杂度中的 $ k $ 去掉,变成 $ O(n2^k) $ ,具体貌似是利用“基”的形式进行 $ O(2^k) $ 的FWT,具体可以看她的这篇博客:https://www.cnblogs.com/Menhera/p/9514412.html (那不足50行的代码真是震撼我心)

Menhera:“我的这个做法是什么点分治的优化版,而你的是弱化版~”(事实是我1700MS她1200MS。。。明明去了一个log然而感觉就像卡了常一样)

只感觉智商被碾压qwq。。。Menhera太强了orz

HDU 5977 Garden of Eden (树形dp+快速沃尔什变换FWT)的更多相关文章

  1. HDU - 5977 Garden of Eden (树形dp+容斥)

    题意:一棵树上有n(n<=50000)个结点,结点有k(k<=10)种颜色,问树上总共有多少条包含所有颜色的路径. 我最初的想法是树形状压dp,设dp[u][S]为以结点u为根的包含颜色集 ...

  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. hdu 5977 Garden of Eden(点分治+状压)

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

  4. HDU 5977 Garden of Eden

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

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

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

  6. hdu 4514 并查集+树形dp

    湫湫系列故事——设计风景线 Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Tot ...

  7. [HDU 5293]Tree chain problem(树形dp+树链剖分)

    [HDU 5293]Tree chain problem(树形dp+树链剖分) 题面 在一棵树中,给出若干条链和链的权值,求选取不相交的链使得权值和最大. 分析 考虑树形dp,dp[x]表示以x为子树 ...

  8. 一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记

    一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记 曾经某个下午我以为我会了FWT,结果现在一丁点也想不起来了--看来"学"完新东西不经常做题不写博客,就白学了 = = 我没啥智 ...

  9. 快速沃尔什变换FWT

    快速沃尔什变换\(FWT\) 是一种可以快速完成集合卷积的算法. 什么是集合卷积啊? 集合卷积就是在集合运算下的卷积.比如一般而言我们算的卷积都是\(C_i=\sum_{j+k=i}A_j*B_k\) ...

随机推荐

  1. 题解-POI2007 OSI-Axes of Symmetry

    Problem bzoj1100 题意概要:给定一个简单多边形(不一定凸),求其对称轴数量 数据组数\(\leq 10\),多边形点数\(\leq 10^5\) Solution 这题算是跨界算法的经 ...

  2. 线性回归,逻辑回归,神经网络,SVM的总结

    目录 线性回归,逻辑回归,神经网络,SVM的总结 线性回归,逻辑回归,神经网络,SVM的总结 详细的学习笔记. markdown的公式编辑手册. 回归的含义: 回归就是指根据之前的数据预测一个准确的输 ...

  3. 第八次作业(课堂实战)- 项目UML设计(团队)

    1. 团队信息 队名:小白吃队 成员: 后敬甲 031602409 卢泽明 031602328 蔡文斌 031602301 葛亮 031602617 刘浩 031602423 黄泽 031602317 ...

  4. sonar——"entrySet()" should be iterated when both the key and value are needed

    When only the keys from a map are needed in a loop, iterating the keySet makes sense. But when both ...

  5. web缓存服务器varnish-4.1.6的部署及配置详解

    web缓存服务器varnish-4.1.6的部署及配置详解 1.安装varnish4.1.6安装依赖 yum install -y autoconf automake jemalloc-devel l ...

  6. oracle:10g下载地址(转载)

    转载地址:http://www.veryhuo.com/a/view/177074.html Oracle 10g Database和Client多平台官方下载地址 http://www.veryhu ...

  7. Expm 10_2 实现Ford-Fulkerson算法,求出给定图中从源点s到汇点t的最大流,并输出最小割。

    package org.xiu68.exp.exp10; import java.util.ArrayDeque; import java.util.ArrayList; import java.ut ...

  8. oracle 报表带小计合计

    selectcase when (grouping(glbm)=1) then '合计' else DECODE(glbm,null,'',glbm) end glbm,case when (grou ...

  9. 3)django-路由系统url

    一:django路由系统说明 路由都在urls文件里,它将浏览器输入的url映射到相应的业务处理逻辑 二:django 常用路由系统配置  1)URL常用有模式一FBV(function base v ...

  10. Android 设备的CPU类型(通常称为”ABIs”)

    armeabiv-v7a: 第7代及以上的 ARM 处理器.2011年15月以后的生产的大部分Android设备都使用它. arm64-v8a: 第8代.64位ARM处理器,很少设备,三星 Galax ...