HDU 5977 Garden of Eden (树形dp+快速沃尔什变换FWT)
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)的更多相关文章
- HDU - 5977 Garden of Eden (树形dp+容斥)
题意:一棵树上有n(n<=50000)个结点,结点有k(k<=10)种颜色,问树上总共有多少条包含所有颜色的路径. 我最初的想法是树形状压dp,设dp[u][S]为以结点u为根的包含颜色集 ...
- 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 ...
- hdu 5977 Garden of Eden(点分治+状压)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5977 题解:这题一看就知道是状压dp然后看了一下很像是点分治(有点明显)然后就是简单的点分治+状压dp ...
- HDU 5977 Garden of Eden
题解: 路径统计比较容易想到点分治和dp dp的话是f[i][j]表示以i为根,取了i,颜色数状态为j的方案数 但是转移这里如果暴力转移就是$(2^k)^2$了 于是用FWT优化集合或 另外http: ...
- HDU 5977 Garden of Eden (树分治+状态压缩)
题意:给一棵节点数为n,节点种类为k的无根树,问其中有多少种不同的简单路径,可以满足路径上经过所有k种类型的点? 析:对于路径,就是两类,第一种情况,就是跨过根结点,第二种是不跨过根结点,分别讨论就好 ...
- hdu 4514 并查集+树形dp
湫湫系列故事——设计风景线 Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)Tot ...
- [HDU 5293]Tree chain problem(树形dp+树链剖分)
[HDU 5293]Tree chain problem(树形dp+树链剖分) 题面 在一棵树中,给出若干条链和链的权值,求选取不相交的链使得权值和最大. 分析 考虑树形dp,dp[x]表示以x为子树 ...
- 一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记
一个数学不好的菜鸡的快速沃尔什变换(FWT)学习笔记 曾经某个下午我以为我会了FWT,结果现在一丁点也想不起来了--看来"学"完新东西不经常做题不写博客,就白学了 = = 我没啥智 ...
- 快速沃尔什变换FWT
快速沃尔什变换\(FWT\) 是一种可以快速完成集合卷积的算法. 什么是集合卷积啊? 集合卷积就是在集合运算下的卷积.比如一般而言我们算的卷积都是\(C_i=\sum_{j+k=i}A_j*B_k\) ...
随机推荐
- 【转】Java并发编程:如何创建线程?
一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过 ...
- NOI2019 SX 模拟赛 no.5
Mas 的童年 题目描述:不知道传送门有没有用? 反正就是对于每个前缀序列求一个断点,使得断点左右两个区间的 分别的异或和 的和最大 分析 jzoj 原题? 但是我 TM 代码没存账号也过期了啊! 然 ...
- webp 图形文件操作工具包 win32 (编译 libwebp-20171228-664c21dd 版本)
源码下载地址 https://chromium.googlesource.com/webm/libwebp/ 版本 libwebp-20171228-664 ...
- ES--05
第四十一讲!分词器内部组成 内置分词器 课程大纲 1.什么是分词器 切分词语,normalization(提升recall召回率) 给你一段句子,然后将这段句子拆分成一个一个的单个的单词,同时对每个单 ...
- 压缩JS的eclipse插件
主页:http://jscompressor.oncereply.me/ Update site: http://jscompressor.oncereply.me/update/
- highcharts之柱状图
<div class="row"> <div class="col-md-12"> <div id="container ...
- Oracle PGA作用&work_mode
专有模式下ORACLE会给每个连接分配一个服务进程(Server Process),这个服务进程将为这个连接服务.为这个服务进程分配的内存叫做PGA.PGA不需要Latch也不需要Lock,永远不会发 ...
- 【进阶3-4期】深度解析bind原理、使用场景及模拟实现(转)
这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://github.com/yygmind/blog/issues/23 bind() bind() 方法会创建一个新函数,当这个新函 ...
- C#实现向excel中插入行列,以及设置单元格合并居中效果
插入空行: Microsoft.Office.Interop.Excel.Workbook xlsWorkbook; Microsoft.Office.Interop.Excel.Worksheet ...
- ionic3 出现莫名广告
应用上线出现 有莫名其妙的广告弹出. 1,DNS被劫持 2,第三方包带广告 3,Http被劫持 wifi和4G网都出现了广告,所以可以直接排除DNS被劫持的问题 广告页只会在H5的页面出现,所以基本可 ...