qwq
首先,如果是没有要求本质不同的话,那么还是比较简单的一个树形dp

我们令dp[i][0/1]dp[i][0/1]dp[i][0/1]表示是否iii的子树,是否选iii这个点的方案数。

一个比较显然的想法。

dp[i][0]=∏(dp[p][0]+dp[p][1])dp[i][0]=\prod (dp[p][0]+dp[p][1])dp[i][0]=∏(dp[p][0]+dp[p][1])
dp[i][1]=∏dp[p][0]dp[i][1]=\prod dp[p][0]dp[i][1]=∏dp[p][0]

最后直接将一号点的答案加起来就好。

qwq但是如果写一发,就会发现第二个样例就wei掉了
(因为题目要求本质不同)

qwq
那么这个东西应该怎么做呢。

因为本质不同,所以对于xxx形态和yyy形态,在同一个根的两个形态一样的子树中,顺序并不影响贡献的。

所以考虑如果存在相同的应该怎么计算答案。

为了避免出现子树和整棵树除去这个子树之后的形态相似。
我们选择用重心来当做整个树的根。
因为重心的每个儿子都小于size2\frac{size}{2}2size​
(如果存在两个重心的话,一个比较简便的计算方法就是用一个新点,连接两个点)

那么由于题目中的本质不同是旋转同构,所以如果两个子树形态相同,但是处于不同的子树,不需要一起计算(因为没法保证其他部分还相同)

所以我们就要求的是,对于每一个子树,求他本质相同的子树的贡献。

那么同一个形态的子树应该怎么统计答案呢。

 int p = (dp[v[i-1]][0]+dp[v[i-1]][1])%mod;int pp = dp[v[i-1]][0]%mod;p%=mod;pp%=mod;//dp[x][0]=(dp[x][0]*C(p+now-1,p-1))%mod;//dp[x][1]=(dp[x][1]*C(pp+now-1,pp-1))%mod;dp[x][0]=(dp[x][0]*C(p+now-1,now))%mod;dp[x][1]=(dp[x][1]*C(pp+now-1,now))%mod;//隔板,允许为空//由于要求本质不同,所以我们相当于对于这now个同形态子树,放到对应的方案数的箱子里,允许为空,所以是隔板法、//因为AAB和BAA是本质相同的。   now=1;

因为AAB和BAA是本质相同的。
所以其实我们将相当于把一些方案数,放到不同的子树内部。因为影响的答案只会是每个形态的个数。
这个是一个组合数,如果方案数是ppp,然后同形态的子树个数是nownownow,
Cp+now−1nowC^{now}_{p+now-1}Cp+now−1now​

实际上这个是隔板(允许为空),一个经典套路

但是为了计算方便,我们选择选与他对称的一个组合数进行计算,因为nownownow的范围是O(n)O(n)O(n)的,我们可以直接用循环求组合数(其实也没别的办法啊)

那么现在就剩下一个问题了,就是如何判断形态相同。
这时候就需要树哈希了
sro DT_Kang orz

这里用的是亢爷的一种树哈希的做法。

void dfs1(int x,int fa)
{int num=0;vector<int> v;v.clear();ha[x]=1;for (int i=point[x];i;i=nxt[i]){int p = to[i];if (p==fa) continue;dfs1(p,x);}for (int i=point[x];i;i=nxt[i]){int p = to[i];if (p==fa) continue;v.pb(p);num++;}sort(v.begin(),v.end(),cmp);for (int i=0;i<num;i++){ha[x]=(ha[x]+ha[v[i]]*mi[i+1])%mod;}ha[x]=ha[x]*size[x]%mod;
}

首先,我们对于每个子树,统计他的每个儿子,按照哈希值进行排序,从小到大乘上mimimi,然后最后用整个的哈希值,乘上子树大小
(可以直接当成一个套路去记)

下面弄上整个代码。

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#include<set>
#define int long long
#define pb push_backusing namespace std;inline int read()
{int x=0,f=1;char ch=getchar();while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;
}const int maxn = 1e6+1e2;
const int maxm = 2*maxn;
const int mod = 1e9+7;
const int qwq = 2333;int point[maxn],nxt[maxm],to[maxm];
int cnt=1,n,m;
int ha[maxn];
int size[maxn],root,root1,root2;
int dp[maxn][2];
int mx[maxn];
int mi[maxn];
int power[maxn],inv[maxn];
int maxson,ymh=1e9;void addedge(int x,int y)
{nxt[++cnt]=point[x];to[cnt]=y;point[x]=cnt;
}int qsm(int i,int j)
{int ans=1;while (j){if (j&1) ans=ans*i%mod;j>>=1;i=i*i%mod;}return ans;
}int C(int n,int m)
{n=n%mod;m=(m+mod)%mod;int ans=1;for (int i=n-m+1;i<=n;i++) ans=ans*i%mod;//if (inv[m]==0) cout<<m<<"()()("<<endl;ans=ans*inv[m]%mod;//int ss=ans+1;return ans;
}void init(int n)
{n+=10;power[0]=1;inv[0]=1;for (int i=1;i<=n;i++) power[i]=power[i-1]*i%mod;inv[n]=qsm(power[n],mod-2);for (int i=n-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;mi[0]=1;for (int i=1;i<=n;i++) mi[i]=mi[i-1]*qwq%mod;
}void getroot(int x,int fa)
{size[x]=1;for (int i=point[x];i;i=nxt[i]){int p = to[i];if (p==fa) continue;getroot(p,x);size[x]+=size[p];mx[x]=max(mx[x],size[p]);}mx[x]=max(mx[x],n-size[x]);if (mx[x]<ymh){ymh=mx[x];root1=x;}else if (mx[x]==ymh) root2=root1,root1=x;
}void dfs(int x,int fa)
{size[x]=1;for (int i=point[x];i;i=nxt[i]){int p = to[i];if (p==fa) continue;dfs(p,x);size[x]+=size[p];}
}int a[maxn];bool cmp(int a,int b) //sro DT_Kang orz
{return ha[a]<ha[b];
}void dfs1(int x,int fa)
{int num=0;vector<int> v;v.clear();ha[x]=1;for (int i=point[x];i;i=nxt[i]){int p = to[i];if (p==fa) continue;dfs1(p,x);}for (int i=point[x];i;i=nxt[i]){int p = to[i];if (p==fa) continue;v.pb(p);num++;}sort(v.begin(),v.end(),cmp);for (int i=0;i<num;i++){ha[x]=(ha[x]+ha[v[i]]*mi[i+1])%mod;}ha[x]=ha[x]*size[x]%mod;
}void solve(int x,int fa)
{dp[x][0]=dp[x][1]=1;int num=0;vector<int> v;v.clear();for (int i=point[x];i;i=nxt[i]){int p = to[i];if (p==fa) continue;solve(p,x);v.pb(p);num++;}sort(v.begin(),v.end(),cmp);int now = 1;v.pb(n+2);for (int i=1;i<=num;i++){//cout<<v[i]<<"***"<<" ";if (ha[v[i]]!=ha[v[i-1]]){int p = (dp[v[i-1]][0]+dp[v[i-1]][1])%mod;int pp = dp[v[i-1]][0]%mod;p%=mod;pp%=mod;//dp[x][0]=(dp[x][0]*C(p+now-1,p-1))%mod;//dp[x][1]=(dp[x][1]*C(pp+now-1,pp-1))%mod;dp[x][0]=(dp[x][0]*C(p+now-1,now))%mod;dp[x][1]=(dp[x][1]*C(pp+now-1,now))%mod;//隔板,允许为空//由于要求本质不同,所以我们相当于对于这now个同形态子树,放到对应的方案数的箱子里,允许为空,所以是隔板法、//因为AAB和BAA是本质相同的。  now=1;}elsenow++;//cout<<v[i]<<endl;}//if (dp[x][0]==0) cout<<x<<" "<<dp[x][0]<<endl;
}signed main()
{n=read();ha[n+2]=mod+2;init(n);for (int i=1;i<n;i++){int x=read(),y=read();addedge(x,y);addedge(y,x);}getroot(1,0); //求重心 if (mx[root2]==ymh){root=n+1;for (int i=point[root1];i;i=nxt[i]){int p = to[i];   if (p==root2){to[i]=to[i^1]=n+1;break;}    }addedge(root,root1);addedge(root,root2);}else{root = root1;}// cout<<root<<endl;memset(size,0,sizeof(size));dfs(root,0);dfs1(root,0);// cout<<1<<endl;solve(root,0);//cout<<root<<endl;//cout<<root1<<" "<<root2<<endl;//cout<<1<<endl;if (root==root1){//cout<<"****"<<endl;cout<<(dp[root][0]+dp[root][1])%mod;return 0;} int ans=0;if (ha[root1]==ha[root2]){int p = dp[root2][0]%mod;ans=(dp[root1][1]*dp[root2][0]%mod+C(p+2-1,2))%mod;}else{//cout<<"*****************"<<endl;ans=(dp[root1][1]*dp[root2][0]%mod+dp[root1][0]*dp[root2][1]%mod+dp[root1][0]*dp[root2][0]%mod)%mod;}cout<<ans;return 0;
}

洛谷4895 独钓寒江雪 (树哈希+dp+组合)相关推荐

  1. 洛谷P2507 [SCOI2008]配对 题解(dp+贪心)

    洛谷P2507 [SCOI2008]配对 题解(dp+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/1299251 链接题目地址:洛谷P2507 [S ...

  2. 洛谷 P3373 线段树2

    洛谷 P3373 线段树2 mul和pls更新某区间左右子树sum的时候,别忘了回头更新这个区间的sum 只有在传递给子序列之后,父序列的lz标记才能清零.其他时候,lz标记只增不减 #include ...

  3. 洛谷P3373线段树

    洛谷P3373 线段树模板题,主要对懒标的处理要求比较高. 有三种操作: 区间加法 区间乘法 区间求和查询 tips:我们对一个区间进行乘k操作的时候,他之前可能存在加法lazy还没pushdown, ...

  4. 洛谷4895 BZOJ3162 独钓寒江雪 树形dp 树哈希

    题目链接 题意: 给定一棵无根树,求其中本质不同的独立集的个数.独立集就是一个集合中的点之间都没有边直接相连.n<=5e5n<=5e5n<=5e5,对1e9+71e9+71e9+7取 ...

  5. bzoj 3162: 独钓寒江雪 树哈希+树形dp

    题意 给出一棵无标号无根树,问本质不同的最大独立集数量.答案模1e9+7. n<=500000 分析 对于一般的情况,我们可以先找出树的重心作为根,然后进行树形dp.这样做有什么好处呢?通过根的 ...

  6. 洛谷P4322 最佳团伙(树上dp)

    题目描述 洛谷传送门 题目描述 JSOI 信息学代表队一共有 N 名候选人,这些候选人从 1 到 N 编号.方便起见,JYY 的编号是 0 号.每个候选人都由一位编号比他小的候选人Ri推荐.如果 Ri ...

  7. 洛谷 P3205 [HNOI2010]合唱队(区间dp)

     题目链接: [HNOI2010]合唱队 - 洛谷https://www.luogu.com.cn/problem/P3205  思路: 这题我是看了题解才做出来的,贴一下这位大佬的博客题解 P320 ...

  8. 洛谷P1006 传纸条 (棋盘dp)

    好气,在洛谷上交就过了,在caioj上交就只有40分 之前在51nod做过这道题了. https://blog.csdn.net/qq_34416123/article/details/8180902 ...

  9. 【题解】洛谷P4158 [SCOI2009] 粉刷匠(DP)

    次元传送门:洛谷P4158 思路 f[i][j][k][0/1]表示在坐标为(i,j)的格子 已经涂了k次 (0是此格子涂错 1是此格子涂对)涂对的格子数 显然的是 每次换行都要增加一次次数 那么当j ...

最新文章

  1. 单词缩写(abbr.cpp)每日一题
  2. 袋鼯麻麻——智能购物平台
  3. bzoj1110: [POI2007]砝码Odw
  4. 解决OutOfMemoryError: unable to create new native thread问题
  5. 基于水色图像的水质评价
  6. c语言rtu crc16,Modbus-RTU-crc16校验方法C语言实现
  7. 查看照片的指定位置的像素点值,并在照片中绘制一条指定像素颜色的线段
  8. Docx:docx.opc.exceptions.PackageNotFoundError: Package not found at
  9. AE物体表面跟踪特效合成高级插件:Lockdown for Mac 支持ae2021
  10. Python | threading04 - 使用信号量,实现线程间同步
  11. windows 笔记本连接公共wifi不弹出登录页面的处理办法
  12. CleanCodeHandbook Chapter 9: Binary Search(48-50)
  13. python爬虫-33个Python爬虫项目实战(推荐)
  14. python模拟内置函数all_python内置函数all和any
  15. ioswebview混编_iOS与H5混编--优秀的第三方框架WebViewJavascriptBridge
  16. vsftpd之启用OpenSSL认证
  17. python小说爬虫练习
  18. StringBuffer 拼接字符串时,删除最后一个逗号
  19. 贝叶斯网络、拉普拉斯平滑
  20. 医疗系统流程软件测试用例,如何写全流程的测试用例 - rose8561900的个人空间 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...

热门文章

  1. 推荐25个上网必备的经典网站
  2. js中一种常见条件判断if(var)的坑
  3. 跨考西电计算机科学与技术研究生经验贴,西安交通大学912计算机133分经验分享...
  4. python查看显存占用情况以及使用numba.cuda释放显存
  5. 如何在网络上传输中文
  6. openjudge 1.8.24 蛇形填充数组
  7. 晶振保存和使用中的注意事项
  8. MS开始提供Windows Vista beta2下载
  9. Linux下WPS相关命令——et,wps,wpp
  10. 后台成功返回succ,但是前端data.result =='succ' 并不成立