原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ23.html

题目传送门 - UOJ#23

题意

  给定一个有 n 个节点的仙人掌(可能有重边)。

  对于所有的 $L(1\leq L\leq n-1)$ ,求出有多少不同的从节点 1 出发的包含 L 条边的简单路径。简单路径是指不重复经过任意一点。

  $n\leq 10^5$

题解

  首先我们把走一条边看作多项式 $x^1$ ,那么一条长度为 L 的路径就是其路径上的多项式的乘积。

  接下来称“环根”为距离节点 1 最近的那个节点。

  假设 $p_v$ 为点 v 对最终答案的贡献,那么 $p_v$ 是什么?分两种情况讨论:

    1. v 属于某个环且 v 不是该环的环根。那么显然这个环的环根(设为 u)更靠近 1 ,假设 v 到 u 的两条路径长度分别是 a 和 b ,那么 $p_v=p_u \times (x^a+x^b)$ 。

    2. 假设有一个点 u 不和 v 在同一个环中,且 u 距离 1 比 v 距离 1 更近,那么 $p_v=p_v\times x$ 。

  于是到此为止我们已经可以轻松的解决 50 分了。

  然而跳蚤国的疆域着实太大……

  考虑对于仙人掌进行点分治,然后对于当前连通块 $T$ ,我们找点分中心 $x$ 。

  怎么找点分中心?——类比找树的点分中心,"找一个结点,把和它相连的边都断了并且他在的每一个环上的边都要去掉(不去掉环上的其它结点)。这样找出连通块最大的最小作为重心。"

  定义连通块的根为当前连通块中在原图距离 1 最近的点。

  于是我们可以分治 FFT 求出点分中心 x 到 当前连通块的根 的多项式。

  结合点分治,总复杂度为 $O(n\log^3 n)$ ,可能可以通过此题。

——VFleaKing

  对于当前点分中心 x ,如果我们先将比 x 靠近连通块根的那个子仙人掌处理了,那么,我们会发现我们在这时要算的多项式大部分已经算好了。这里只需要做的就是合并。可以发现,从 x 追根溯源得到的这些多项式有 $O(\log n)$ 个,而且他们的最高次项指数是和对应的连通块的 size 相关的,又由于对于这些连通块,我们做的是点分治,所以这些多项式的最高次项指数是大约 2 倍两倍增长的。所以现在求点分中心 x 到 当前连通块的根 的多项式,只需要 $T(n) = T(n/2) + O(n\log  n) = O(n\log n)$ 的时间复杂度。

  结合点分治,我们可以得到一个 $O(n\log ^2 n)$ 的优秀做法。

  当然,底层实现的时候还是有一些细节需要注意,这里就不展开赘述了(饶了我吧,懒得写了……)。

  由于博主人傻常数大,所以用时差点吃鸡了。

  顺便赠送样例 2 的图,以及其圆方树。

代码

#include <bits/stdc++.h>
int Debug=0;
using namespace std;
typedef long long LL;
LL read(){
LL x=0;
char ch=getchar();
while (!isdigit(ch))
ch=getchar();
while (isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=(1<<18)+9,mod=998244353;
int Log[N];
int Pow(int x,int y){
int ans=1;
for (;y;y>>=1,x=(LL)x*x%mod)
if (y&1)
ans=(LL)ans*x%mod;
return ans;
}
void Add(int &x,int y){
if ((x+=y)>=mod)
x-=mod;
}
int del(int x,int y){
return x-y<0?x-y+mod:x-y;
}
int rev[N],w[N],A[N],B[N];
void FFT(int a[],int n){
for (int i=0;i<n;i++)
if (rev[i]<i)
swap(a[i],a[rev[i]]);
for (int t=n>>1,d=1;d<n;d<<=1,t>>=1)
for (int i=0;i<n;i+=(d<<1))
for (int j=0;j<d;j++){
int tmp=(LL)w[j*t]*a[i+j+d]%mod;
a[i+j+d]=del(a[i+j],tmp);
Add(a[i+j],tmp);
}
}
vector <int> Mul(vector <int> a,vector <int> b){
static vector <int> ans;
int n=1,d=0;
for (;n<a.size()+b.size();n<<=1,d++);
w[0]=1,w[1]=Pow(3,(mod-1)/n);
for (int i=2;i<n;i++)
w[i]=(LL)w[i-1]*w[1]%mod;
for (int i=0;i<n;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(d-1));
for (int i=0;i<n;i++)
A[i]=B[i]=0;
for (int i=0;i<a.size();i++)
A[i]=a[i];
for (int i=0;i<b.size();i++)
B[i]=b[i];
FFT(A,n),FFT(B,n);
for (int i=0;i<n;i++)
A[i]=(LL)A[i]*B[i]%mod;
w[1]=Pow(w[1],mod-2);
for (int i=2;i<n;i++)
w[i]=(LL)w[i-1]*w[1]%mod;
FFT(A,n);
int inv=Pow(n,mod-2);
for (int i=0;i<n;i++)
A[i]=(LL)A[i]*inv%mod;
while (n>0&&!A[n-1])
n--;
ans.clear();
for (int i=0;i<n;i++)
ans.push_back(A[i]);
return ans;
}
struct poly{
vector <int> v;
poly(){}
poly(int x){
v.clear();
while (x>=(int)v.size())
v.push_back(0);
v[x]++;
}
void add_move(poly &p,int x){
int s=max(v.size(),p.v.size()+x);
while (v.size()<s)
v.push_back(0);
for (int i=0;i<p.v.size();i++)
Add(v[i+x],p.v[i]);
}
poly operator << (int x){
poly res=*this;
reverse(res.v.begin(),res.v.end());
while (x--)
res.v.push_back(0);
reverse(res.v.begin(),res.v.end());
return res;
}
void operator += (poly A){
while (v.size()<A.v.size())
v.push_back(0);
for (int i=0;i<A.v.size();i++)
Add(v[i],A.v[i]);
}
void operator *= (poly A){
v=Mul(v,A.v);
}
}C;
poly operator + (poly A,poly B){
C.v.clear();
for (int i=max(A.v.size(),B.v.size());i>0;i--)
C.v.push_back(0);
for (int i=0;i<A.v.size();i++)
Add(C.v[i],A.v[i]);
for (int i=0;i<B.v.size();i++)
Add(C.v[i],B.v[i]);
return C;
}
poly operator * (poly A,poly B){
C.v=Mul(A.v,B.v);
return C;
}
struct Gragh{
static const int M=N*4;
int cnt,y[M],nxt[M],fst[N];
void clear(){
cnt=1;
memset(fst,0,sizeof fst);
}
void add(int a,int b){
y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt;
}
}g;
int n,m,k;
int fa[N];
int dfn[N],low[N],st[N],Time=0,st_top=0,vise[N*2];
vector <int> circle[N],T[N];
void T_add(int a,int b){
T[a].push_back(b);
T[b].push_back(a);
}
void Tarjan(int x){
dfn[x]=low[x]=++Time,st[++st_top]=x;
for (int i=g.fst[x];i;i=g.nxt[i]){
if (vise[i>>1])
continue;
vise[i>>1]=1;
int y=g.y[i];
if (!dfn[y]){
Tarjan(y);
low[x]=min(low[x],low[y]);
if (low[y]>dfn[x]){
T_add(x,y);
fa[y]=x;
st_top--;
}
else if (low[y]==dfn[x]){
k++;
T_add(x,k);
circle[k].clear();
circle[k].push_back(x);
while (st[st_top]!=x&&low[st[st_top]]==dfn[x]){
T_add(k,st[st_top]);
circle[k].push_back(st[st_top]);
fa[st[st_top]]=x;
st_top--;
}
}
}
else
low[x]=min(low[x],dfn[y]);
}
}
int fad1[N],fad2[N];
int size[N],fimax[N],semax[N],vis[N],top[N];
int Tfa[N],Tdepth[N];
void dfsT(int x,int pre,int d){
Tfa[x]=pre,Tdepth[x]=d;
for (auto y : T[x])
if (y!=pre)
dfsT(y,x,d+1);
}
int Size,RT;
void get_size(int x,int pre){
size[x]=x<=n?1:0,fimax[x]=semax[x]=0;
for (auto y : T[x])
if (y!=pre&&!vis[y]){
get_size(y,x);
size[x]+=size[y];
if (size[y]>fimax[x])
semax[x]=fimax[x],fimax[x]=size[y];
else if (size[y]>semax[x])
semax[x]=size[y];
}
}
void get_root(int x,int pre){
if (x<=n){
fimax[x]=semax[x]=0;
int f=Tfa[x];
if (!vis[f]){
if (f<=n)
fimax[x]=Size-size[x];
else
fimax[x]=size[x]==fimax[f]?semax[f]:fimax[f];
}
}
else {
if (Size-size[x]>fimax[x])
semax[x]=fimax[x],fimax[x]=Size-size[x];
else if (Size-size[x]>semax[x])
semax[x]=Size-size[x];
}
for (auto y : T[x])
if (y!=pre&&!vis[y]){
if (x<=n)
fimax[x]=max(fimax[x],y<=n?size[y]:fimax[y]);
get_root(y,x);
}
if (x<=n)
if (!RT||fimax[RT]>fimax[x])
RT=x;
}
poly up[N],dn[N],po,potmp;
int d_solve=0;
void solve(int x){
get_size(x,0);
Size=size[x],RT=0;
get_root(x,0);
assert(RT!=0);
vis[x=RT]=++Time;
top[x]=x;
while (!vis[fa[top[x]]])
top[x]=fa[top[x]];
dn[x].v.push_back(0);
Add(dn[x].v[0],1);
for (auto y : T[x])
if (!vis[y])
if (y<=n){
solve(y);
if (Tdepth[y]>Tdepth[x])
dn[x].add_move(dn[y],1);
}
else {
vis[y]=vis[x];
for (auto z : T[y])
if (!vis[z]&&z!=x){
solve(z);
if (Tdepth[z]>Tdepth[y]){
dn[y].add_move(dn[z],fad1[z]);
dn[y].add_move(dn[z],fad2[z]);
}
}
if (Tdepth[y]>Tdepth[x])
dn[x].add_move(dn[y],0);
}
up[x].v.clear();
if (vis[fa[x]]>vis[x]){
int f=fa[x];
po.v.clear();
po=poly(0);
while (f!=top[x]){
if (top[f]==f){
if (Tfa[f]>n){
potmp.v.clear();
potmp=po;
po.v.clear();
po.add_move(potmp,fad1[f]);
po.add_move(potmp,fad2[f]);
}
else
po=po<<1;
f=fa[f];
}
else {
po*=up[f];
f=top[f];
}
}
if (Tfa[x]>n){
top[fa[x]]=top[x];
up[fa[x]]=po;
up[x].add_move(po,fad1[x]);
up[x].add_move(po,fad2[x]);
}
else
up[x].add_move(po,1);
}
else
up[x].v.push_back(1);
if (top[x]!=x)
dn[top[x]]+=dn[x]*up[x];
if (vis[Tfa[x]]>=vis[x]&&Tfa[x]>n)
dn[top[x]]+=dn[Tfa[x]]*up[fa[x]];
}
int main(){
Log[1]=0;
for (int i=2;i<N;i++)
Log[i]=Log[i>>1]+1;
n=k=read(),m=read();
g.clear();
for (int i=1;i<=m;i++){
int a=read(),b=read();
g.add(a,b);
g.add(b,a);
}
Tarjan(1);
for (int i=n+1;i<=k;i++){
int tmp=circle[i].size();
for (int j=1;j<tmp;j++){
fad1[circle[i][j]]=j;
fad2[circle[i][j]]=tmp-j;
}
}
dfsT(1,0,0);
vis[0]=1;
for (int i=1;i<=k;i++)
dn[i].v.clear();
Time=0;
solve(1);
while (dn[1].v.size()<n)
dn[1].v.push_back(0);
for (int i=1;i<n;i++)
printf("%d\n",dn[1].v[i]);
return 0;
}

  

UOJ#23. 【UR #1】跳蚤国王下江南 仙人掌 Tarjan 点双 圆方树 点分治 多项式 FFT的更多相关文章

  1. 【题解】Uoj#30 Tourist(广义圆方树+树上全家桶)

    [题解]Uoj#30 Tourist(广义圆方树+树上全家桶) 名字听起来很霸气其实算法很简单.... 仙人掌上的普通圆方树是普及题,但是广义圆方树虽然很直观但是有很多地方值得深思 说一下算法的流程: ...

  2. UOJ.87.mx的仙人掌(圆方树 虚树)(未AC)

    题目链接 本代码10分(感觉速度还行..). 建圆方树,预处理一些东西.对询问建虚树. 对于虚树上的圆点直接做:对于方点特判,枚举其所有儿子,如果子节点不在该方点代表的环中,跳到那个点并更新其val, ...

  3. UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...

  4. C++版 - 剑指offer 面试题23:从上往下打印二叉树(二叉树的层次遍历BFS) 题解

    剑指offer  面试题23:从上往下打印二叉树 参与人数:4853  时间限制:1秒  空间限制:32768K 提交网址: http://www.nowcoder.com/practice/7fe2 ...

  5. 【Codefoces487E/UOJ#30】Tourists Tarjan 点双连通分量 + 树链剖分

    E. Tourists time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard inpu ...

  6. 在UEFI下安装windows和Ubuntu双系统目前不可行

    UEFI是BIOS的升级,未来将取代BIOS,说白了,就是跟BISO差不多的作用.但是目前比较新的主板兼容两种设置就比较坑了,默认是UEFI,UEFI下只能安装win8以上的版本,和linux64位系 ...

  7. win7下硬盘安装win7+CentOS双系统方法

    原文:win7下硬盘安装win7+CentOS双系统方法 LinuxCentOS安装大致介绍: win7下硬盘安装win7+CentOS双系统方法 原则: 所有的看完在装,请仔细看 一 条件: 1. ...

  8. win7下硬盘安装win7+linuxUbuntu双系统方法

    Linux安装大致介绍: win7下硬盘安装win7+linuxUbuntu双系统方法 原则: 所有的看完在装,请仔细看 一 条件: 1. 系统选择 linux unbuntu12.04.2-desk ...

  9. 【转】Windows下安装python2和python3双版本

    [转]Windows下安装python2和python3双版本 现在大家常用的桌面操作系统有:Windows.Mac OS.ubuntu,其中Mac OS 和 ubuntu上都会自带python.这里 ...

随机推荐

  1. Django ----- 模板2

    tags for <ul> {% for user in user_list %} <li>{{ user.name }}</li> {% endfor %} #结 ...

  2. 2018年Android的保活方案效果统计

    一.常见保活方案 1.监听广播:监听全局的静态广播,比如时间更新的广播.开机广播.解锁屏.网络状态.解锁加锁亮屏暗屏(3.1版本),高版本需要应用开机后运行一次才能监听这些系统广播,目前此方案失效.可 ...

  3. SVG前戏—让你的View多姿多彩

    什么是SVG SVG的全称是Scalable Vector Graphics,叫可缩放矢量图形.是一种基于可扩展标记语言(XML).它和位图(Bitmap)相对,SVG不会像位图一样因为缩放而让图片质 ...

  4. Confluence 6 数据库字符集编码和问题

    数据库字符集编码 你的数据库和 JDBC 数据源连接必须配置为使用 UTF-8 编码(或者根据你配置的数据库来制定正确的 UTF-8 编码字符集,例如在 Oracle 中使用的是 AL32UTF8 ) ...

  5. AD9361寄存器配置顺序,循环模式,自收自发

    :] cmd_data; :] index; begin case(index) 'h000,8'h00};//set spi -- 'h3df,8'h01};//set init -- 'h037, ...

  6. D3.js 添加zoom缩放功能后dblclick双击也会放大的问题

    svg.call(zoom).on("dblclick.zoom", null); https://stackoverflow.com/questions/25007466/d3- ...

  7. Data Preprocess

    本文试图解决一个问题,即我们自定义的数据如何训练模型?初识深度学习,我们接触手写数字识别模型,但是批次数据是mnist已经定义好的,我们现在有自己的图片如何做成批次进行训练模型. 现在我们将准备好的原 ...

  8. linux 基础知识(三)

    抽空把Linux的一些基础的东西再补充一下,安全的东西真的很多都是要自己不断的学习,很多还是今天学习了一点时间过后不用就会忘记.所以学习的东西就是要不断地往复. 有时候感觉有时候快就是慢,慢就是快. ...

  9. 第一周学习总结-Java

    2018年7月15日 暑假第一周,我从网上找了一些讲Java的视频,学到了一些Java的基础,同时也弥补了一些之前学c/c++的知识漏洞.例如,了解到了原码反码补码和按位取反运算符(~)的运算原理. ...

  10. vue和stylus在subline中显示高亮

    首先: 安装这两个插件   Vue Syntax Highlight    和    stylus 1.按住 ctrl + shift + p 2.输入:install Package 3.输入: V ...