Codeforces 1010F - Tree(分治 NTT+树剖)
神仙题。
首先我们考虑按照这题的套路,记 \(t_i\) 表示 \(i\) 上的果子数量减去其儿子果子数量之和,那么对于一个合法的放置果子的方案,必然有 \(t_i\ge 0,\sum\limits_{i=1}^nt_i=X\),而根据隔板法,对于一个大小为 \(s\) 的连通块,分配该连通块中的 \(t_i\) 的方案数为 \(\dbinom{X+s-1}{s-1}\),因此假设大小为 \(i\) 的连通块个数为 \(c_i\),那么答案即为 \(\sum\limits_{i=1}^nc_i\dbinom{X+i-1}{i-1}\),后面那坨组合数显然可以递推求出,因此我们的任务就是求出 \(c_i\)。
考虑树形 DP,\(dp_{i,j}\) 表示 \(i\) 子树内大小为 \(j\) 的连通块有多少个,那么假设 \(u,v\) 为 \(i\) 的两个儿子,那么显然有 \(dp_{i,j}=\sum\limits_{p+q=j-1}dp_{u,p}dp_{v,q}\),\(dp_{i,0}=1\)。直接做是平方的,不过注意到这东西一脸卷的样子,因此考虑设 \(F_i(x)\) 表示 \(dp_i\) 的 OGF,那么有 \(F_i(x)=F_u(x)F_v(x)·x+1\),但是直接卷依然会被卡成 \(n^2\log n\)。这时候就要用到类似于**动态 \(dp\) **的思路了,我们考虑对整棵树进行树链剖分并对于一条重链上的节点我们直接计算其链顶上的 \(dp\) 值,那么假设这条重链上的点从上至下分别是 \(p_1,p_2,p_3,\cdots,p_k\),并且对于每个 \(p_i(i\ne k)\),其不同于 \(p_{i+1}\) 的另一个儿子为 \(q_i\)(如果这样的儿子不存在那我们默认 \(q_i=0,F_{q_i}=1\)),那么显然 \(F_{p_k}=x+1,F_{p_i}=xF_{q_i}F_{p_{i+1}}+1\),递推以下可知 \(F_{p_1}=\sum\limits_{i=1}^k\prod\limits_{j=k-i+1}^kxF_{q_i}\)。因此到这里,该问题可以转化为,有 \(k\) 个生成函数 \(a_1,a_2,\cdots,a_k\),要求 \(a_1+a_1a_2+a_1a_2a_3+\cdots+a_1a_2\cdots a_k\) 的值。该问题可以分治 FFT(其实严格来说不能叫分治 FFT,因为没有 cdq 分治) 求解,具体来说当我们分治到区间 \([l,r]\) 时我们求出 \(A_{l,r}=\prod\limits_{i=l}^ra_i\),以及 \(B_{l,r}=\sum\limits_{i=l}^r\prod\limits_{j=i}^ra_j\),那么设 \(mid=\lfloor\dfrac{l+r}{2}\rfloor\),有 \(A_{l,r}=A_{l,mid}A_{mid+1,r},B_{l,r}=B_{l,mid}+A_{l,mid}B_{mid+1,r}\),分治求解即可。
至于复杂度,大概就对于每个点,其最多会对 \(\log n\) 个多项式的度产生 \(1\) 的贡献,再加上分治 FFT 的复杂度,总复杂度 \(n\log^3n\)
u1s1 vector 实现的 NTT 是真的好写,但常数是真的大……把多项式卷积里的 vector 换成数组后常数竟一下子将为原来的 \(\dfrac{1}{8}\)……
贴个稍微能看点的代码:
const int MAXN=1e5;
const int MAXP=1<<18;
const int pr=3;
const int ipr=332748118;
const int MOD=998244353;
int inv[MAXN+5];
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int rev[MAXP+5],A[MAXP+5],B[MAXP+5],C[MAXP+5];
void NTT(int *a,int len,int type){
int lg=31-__builtin_clz(len);
for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<lg-1);
for(int i=0;i<len;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=2;i<=len;i<<=1){
int W=qpow((type<0)?ipr:pr,(MOD-1)/i);
for(int j=0;j<len;j+=i){
for(int k=0,w=1;k<(i>>1);k++,w=1ll*w*W%MOD){
int X=a[j+k],Y=1ll*a[(i>>1)+j+k]*w%MOD;
a[j+k]=(X+Y)%MOD;a[(i>>1)+j+k]=(X-Y+MOD)%MOD;
}
}
}
if(!~type){
int ivn=qpow(len,MOD-2);
for(int i=0;i<len;i++) a[i]=1ll*a[i]*ivn%MOD;
}
}
vector<int> conv(vector<int> x,vector<int> y){
if(x.size()+y.size()<=16){
vector<int> res(x.size()+y.size()-1);
for(int i=0;i<x.size();i++) for(int j=0;j<y.size();j++)
res[i+j]=(res[i+j]+1ll*x[i]*y[j])%MOD;
return res;
}
int LEN=1;while(LEN<x.size()+y.size()) LEN<<=1;
for(int i=0;i<LEN;i++) A[i]=B[i]=C[i]=0;
for(int i=0;i<x.size();i++) A[i]=x[i];
for(int i=0;i<y.size();i++) B[i]=y[i];
NTT(A,LEN,1);NTT(B,LEN,1);
for(int i=0;i<LEN;i++) C[i]=1ll*A[i]*B[i]%MOD;NTT(C,LEN,-1);
vector<int> res;for(int i=0;i<x.size()+y.size()-1;i++) res.pb(C[i]);
return res;
}
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;ll X;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int siz[MAXN+5],wson[MAXN+5],fa[MAXN+5];
void dfs0(int x,int f){
siz[x]=1;fa[x]=f;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs0(y,x);siz[x]+=siz[y];
if(siz[y]>siz[wson[x]]) wson[x]=y;
}
}
vector<vector<int> > p;
vector<int> dp[MAXN+5];
void addzero(vector<int> &v){v.insert(v.begin(),0);}
vector<int> plus_one(vector<int> v){(v[0]+=1)%=MOD;return v;}
vector<int> minus_one(vector<int> v){(v[0]+=MOD-1)%=MOD;return v;}
vector<int> plus_vec(vector<int> a,vector<int> b){
if(a.size()<b.size()) swap(a,b);b.resize(a.size(),0);
for(int i=0;i<a.size();i++) (a[i]+=b[i])%=MOD;
return a;
}
pair<vector<int>,vector<int> > solve(int l,int r){
if(l==r) return mp(plus_one(p[l]),p[l]);int mid=l+r>>1;
pair<vector<int>,vector<int> > L=solve(l,mid);
pair<vector<int>,vector<int> > R=solve(mid+1,r);
// printf("%d %d %d %d!!!\n",L.fi.size(),L.se.size(),R.fi.size(),R.se.size());
return mp(plus_vec(conv(minus_one(L.fi),R.se),R.fi),conv(L.se,R.se));
}
void dfs(int x){
if(!wson[x]) return dp[x]=vector<int>(2,1),void();
for(int y=x;y;y=wson[y]){
for(int e=hd[y];e;e=nxt[e]){
int z=to[e];if(z==fa[y]||z==wson[y]) continue;
dfs(z);
}
} p.clear();
for(int y=x;y;y=wson[y]){
bool flg=0;
for(int e=hd[y];e;e=nxt[e]){
int z=to[e];if(z==fa[y]||z==wson[y]) continue;
addzero(dp[z]);p.pb(dp[z]);flg=1;
} if(!flg) p.pb(minus_one(vector<int>(2,1)));
} assert(!p.empty());reverse(p.begin(),p.end());
dp[x]=solve(0,p.size()-1).fi;//printf("%d: ",x);
// for(int i=0;i<dp[x].size();i++) printf("%d ",dp[x][i]);
// printf("!\n");
}
int main(){
scanf("%d%lld",&n,&X);X%=MOD;
for(int i=1,u,v;i<n;i++){scanf("%d%d",&u,&v);adde(u,v),adde(v,u);}
dfs0(1,0);dfs(1);int ans=0;
for(int i=(inv[0]=inv[1]=1)+1;i<=n;i++) inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
// for(int i=0;i<dp[1].size();i++) printf("%d%c",dp[1][i]," \n"[i+1==dp[1].size()]);
for(int i=1,t=1;i<dp[1].size();i++){
ans=(ans+1ll*dp[1][i]*t)%MOD;
t=1ll*t*(X+i)%MOD*inv[i]%MOD;
} printf("%d\n",ans);
return 0;
}
Codeforces 1010F - Tree(分治 NTT+树剖)的更多相关文章
- SPOJ375Query on a tree I(树剖+线段树)(询问边)
ιYou are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, ...
- codeforces#1187E. Tree Painting(树换根)
题目链接: http://codeforces.com/contest/1187/problem/E 题意: 给出一颗树,找到一个根节点,使所有节点的子节点数之和最大 数据范围: $2 \le n \ ...
- SPOJ Query on a tree II (树剖||倍增LCA)(占位)
You are given a tree (an undirected acyclic connected graph) with N nodes, and edges numbered 1, 2, ...
- Codeforces Gym 100814C Connecting Graph 树剖并查集/LCA并查集
初始的时候有一个只有n个点的图(n <= 1e5), 现在进行m( m <= 1e5 )次操作 每次操作要么添加一条无向边, 要么询问之前结点u和v最早在哪一次操作的时候连通了 /* * ...
- SCOI2016幸运数字(树剖/倍增/点分治+线性基)
题目链接 loj luogu 题意 求树上路径最大点权异或和 自然想到(维护树上路径)+ (维护最大异或和) 那么有三种方法可以选择 1.树剖+线性基 2.倍增+线性基 3.点分治+线性基 至于线性基 ...
- SPOJ Query on a tree III (树剖(dfs序)+主席树 || Splay等平衡树)(询问点)
You are given a node-labeled rooted tree with n nodes. Define the query (x, k): Find the node whose ...
- POJ3237 Tree(树剖+线段树+lazy标记)
You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edges are numbe ...
- CF 504 E —— Misha and LCP on Tree —— 树剖+后缀数组
题目:http://codeforces.com/contest/504/problem/E 快速查询LCP,可以用后缀数组,但树上的字符串不是一个序列: 所以考虑转化成序列—— dfs 序! 普通的 ...
- 【XSY3306】alpha - 线段树+分治NTT
题目来源:noi2019模拟测试赛(一) 题意: 题解: 这场三道神仙概率期望题……orzzzy 这题暴力$O(n^2)$有30分,但貌似比正解更难想……(其实正解挺好想的) 注意到一次操作实际上就是 ...
随机推荐
- Solon 框架如何方便获取每个请求的响应时间?
经常会有同学问 Solon 怎样才能获取每个请求的响应时间?要求是不需要给每个函数加注解.故此,整理了一下. 不给每个函数加注解,主要有两种方式可以获取请求响应时间: 方式1:基于全局过滤器 Solo ...
- 【数据结构与算法Python版学习笔记】递归(Recursion)——优化问题与策略
分治策略:解决问题的典型策略,分而治之 将问题分为若干更小规模的部分 通过解决每一个小规模部分问题,并将结果汇总得到原问题的解 递归算法与分治策略 递归三定律 体现了分支策略 应用相当广泛 排序 查找 ...
- 【c++ Prime 学习笔记】目录索引
第1章 开始 第Ⅰ部分 C++基础 第2章 变量和基本类型 第3章 字符串.向量和数组 第4章 表达式 第5章 语句 第6章 函数 第7章 类 第 Ⅱ 部分 C++标准库 第8章 IO库 第9章 顺序 ...
- 整数划分为k份
题目 将整数n分成k份,且每份不能为空,任意两个方案不能相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5; 1,5,1; 5,1,1; 问有多少种不同的分法. 输入 ...
- 康托展开+逆展开(Cantor expension)详解+优化
康托展开 引入 康托展开(Cantor expansion)用于将排列转换为字典序的索引(逆展开则相反) 百度百科 维基百科 方法 假设我们要求排列 5 2 4 1 3 的字典序索引 逐位处理: 第一 ...
- STM32 PWM功能在关闭时GPIO电平不确定的情况
刚开始接触STM32,遇到一个项目中出现在产品调试中出现在关闭PWM输出时,GPIO电平有不确定的情况.在网上查阅资料发现大神们是这样解释的:PWM在一个脉冲没有结束时关闭输出,会导致GPIO电平不确 ...
- MySQL:互联网公司常用分库分表方案汇总!
一.数据库瓶颈 不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值.在业务Service来看就是,可用数据库连接少甚至无连接可用.接下来就 ...
- Python 爬取 猫眼
1. import requests import re import pymongo MONGO_URL='localhost'#建立连接 MONGO_DB='Maoyan'#创建数据库 clien ...
- Redis | 第一部分:数据结构与对象 上篇《Redis设计与实现》
目录 前言 1. 简单动态字符串 1.1 SDS的定义 1.2 空间预分配与惰性空间释放 1.3 SDS的API 2. 链表 2.1 链表与节点的定义 2.2 链表的API 3. 字典 3.1 哈希表 ...
- Python布尔值
在学到Python数据类型时,发现与大多数语言没什么区别 布尔值可以用 and or not 来运算 and运算是与运算,所有条件都符合才为true >>> True and Tru ...