qwq

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

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

一个比较显然的想法。

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

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

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

qwq但是如果写一发,就会发现第二个样例就wei掉了

(因为题目要求本质不同)

qwq

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

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

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

为了避免出现子树和整棵树除去这个子树之后的形态相似。

我们选择用重心来当做整个树的根。

因为重心的每个儿子都小于\(\frac{size}{2}\)

(如果存在两个重心的话,一个比较简便的计算方法就是用一个新点,连接两个点)

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

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

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

 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是本质相同的。

所以其实我们将相当于把一些方案数,放到不同的子树内部。因为影响的答案只会是每个形态的个数。

这个是一个组合数,如果方案数是\(p\),然后同形态的子树个数是\(now\),

\[C^{now}_{p+now-1}
\]

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

但是为了计算方便,我们选择选与他对称的一个组合数进行计算,因为\(now\)的范围是\(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;
}

首先,我们对于每个子树,统计他的每个儿子,按照哈希值进行排序,从小到大乘上\(mi\),然后最后用整个的哈希值,乘上子树大小

(可以直接当成一个套路去记)

下面弄上整个代码。

// 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_back using 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;
}
else
now++;
//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. 洛谷P3018 [USACO11MAR]树装饰Tree Decoration

    洛谷P3018 [USACO11MAR]树装饰Tree Decoration树形DP 因为要求最小,我们就贪心地用每个子树中的最小cost来支付就行了 #include <bits/stdc++ ...

  2. 洛谷 P3177 [HAOI2015]树上染色 树形DP

    洛谷 P3177 [HAOI2015]树上染色 树形DP 题目描述 有一棵点数为 \(n\) 的树,树边有边权.给你一个在 \(0 \sim n\)之内的正整数 \(k\) ,你要在这棵树中选择 \( ...

  3. 洛谷1087 FBI树 解题报告

    洛谷1087 FBI树 本题地址:http://www.luogu.org/problem/show?pid=1087 题目描述 我们可以把由“0”和“1”组成的字符串分为三类:全“0”串称为B串,全 ...

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

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

  5. 洛谷 P4072 [SDOI2016]征途 斜率优化DP

    洛谷 P4072 [SDOI2016]征途 斜率优化DP 题目描述 \(Pine\) 开始了从 \(S\) 地到 \(T\) 地的征途. 从\(S\)地到\(T\)地的路可以划分成 \(n\) 段,相 ...

  6. 洛谷P3830 随机树(SHOI2012)概率期望DP

    题意:中文题,按照题目要求的二叉树生成方式,问(1)叶平均深度 (2)树平均深度 解法:这道题看完题之后完全没头绪,无奈看题解果然不是我能想到的qwq.题解参考https://blog.csdn.ne ...

  7. NOIP2017提高组Day2T3 列队 洛谷P3960 线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/9265380.html 题目传送门 - 洛谷P3960 题目传送门 - LOJ#2319 题目传送门 - Vij ...

  8. 洛谷P1880 石子合并(区间DP)(环形DP)

    To 洛谷.1880 石子合并 题目描述 在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试设计出1 ...

  9. 洛谷P1063 能量项链(区间DP)(环形DP)

    To 洛谷.1063 能量项链 题目描述 在Mars星球上,每个Mars人都随身佩带着一串能量项链.在项链上有N颗能量珠.能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数.并且,对于相邻的 ...

随机推荐

  1. mycat《对应关系》

  2. mysql switch语句

    SELECT CASE the_order_status WHEN 4 THEN '待收货' WHEN 5 THEN '已收货' ELSE '其他' END AS statuss ,order_id ...

  3. 使用 IDEA 配合 Dockerfile 部署 SpringBoot 工程

    准备 SpringBoot 工程 新建 SpringBoot 项目,默认的端口是 8080 ,新建 Controller 和 Mapping @RestController public class ...

  4. Java基础(四)——抽象类和接口

    一.抽象类 1.介绍 使用关键字 abstract 定义抽象类. abstract定义抽象方法,只有声明,不用实现. 包含抽象方法的类必须定义为抽象类. 抽象类中可以有普通方法,也可以有抽象方法. 抽 ...

  5. 五分钟搞懂MySQL索引下推

    大家好,我是老三,今天分享一个小知识点--索引下推. 如果你在面试中,听到MySQL5.6"."索引优化" 之类的词语,你就要立马get到,这个问的是"索引下推 ...

  6. noip模拟18

    \(\color{white}{\mathbb{曲径通幽,星汉隐约,缥缈灯影,朦胧缺月,名之以:薄雾}}\) 放眼望去前十被我弃掉的 \(t2\) 基本都上85了-- 开考就以为 \(t2\) 是个大 ...

  7. NOIP模拟51

    樱花满地集于我心,楪舞纷飞祈愿相随 前言 太菜了,人手切掉两个题,我竟然一道都不会.. 改 T3 的时候整个人的心态都崩掉了,一部分原因可能是语文素养不高导致我看不懂题解. 另一部分可能就是系太不太好 ...

  8. Prism+WPF使用DependencyInjection实现AutoMapper的依赖注入功能

    前言 在使用PRISM+WPF开发项目的过程中,需要使用AutoMapper实现对象-对象的映射功能.无奈PRISM没有相关对AutoMapper相关的类库,于是转换一下思想,在nuget 中存在有关 ...

  9. 昭山欢node资料学习笔记

    以前学过一片node工作没有用,忘了,趁这个春节在整理一片 第一章 快速塔建一个局哉网服务器 const http = require("http");var server = h ...

  10. Vue3.x全家桶+vite+TS-构建Vue3基本框架

    目录 一.搭建基础项目 1.vite创建项目 3.运行项目 2.环境变量设置介绍 vite配置多环境打包 二.配置Router 1.安装路由 2.配置路由 3.引入 三.配置Vuex 1.安装vuex ...