首页 > 其他分享 >[BZOJ3451] Normal 题解

[BZOJ3451] Normal 题解

时间:2025-01-18 21:32:11浏览次数:1  
标签:sz Normal int 题解 void BZOJ3451 num comn operator

这题分三步:葺网(期望)、淀粉质(点分治)、蓉翅(容斥),再佐以芬芳团(FFT),一道巨难无比的 luogu 黑题就诞生了。

期望

先考虑在淀粉树上,\(i\) 点在 \(j\) 点的子树里的概率。实际上这个问题的每种情况相当于是 \(n\) 个点的各种排列方式。这也就相当于,我们在选择 \(j\) 点之前,没有选择路径 \((i,j)\) 上的其他点,那么 \(j\) 的子树内就会包含 \(i\),否则不会。那么 \(i\) 点在 \(j\) 点子树里的概率就是 \(\dfrac 1{dis(i,j)+1}\)。注意,这里的 \(dis(i,j)\) 是边的数量,而不是点的数量

由于期望具有可加性,所以相当于要求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\dfrac 1{dis(i,j)+1}\)。这玩意可以直接转化为求解 \(num_k=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[dis(i,j)=k]\)。

点分治

此时想到点分治其实水到渠成,关键问题是如何求解。

实际上合并儿子信息的本质就是多项式乘法(因为相当于是两个儿子的信息配对),自然想到 FFT,但是直接把所有都乘到一起正确性会假,用统计“除 \(x\) 子树以外其他子树的信息和”的思想时间复杂度会炸,所以都不行。

容斥

注意力惊人的注意到可以容斥。我们将答案分成两个部分:所有情况(目前考虑的树内的任意两个点可以配对)和错误情况(两个点必须在同一个儿子的子树内),二者相减即为答案。实际上这两个东西都是子树内所有点的深度所得的多项式直接平方的结果。

这样就可以保证在 \(O(m\log m)\) 的时间复杂度内完成合并,加上点分治,时间复杂度为 \(O(n\log^2n)\)。

#include<bits/stdc++.h>
#define stp(x) fixed<<setprecision(x)
using namespace std;
const int N=1e5+5;
const long double pi=acos(-1);
namespace FFT{
    struct comn{long double a,b;};
    struct dft{vector<comn>fg;};
    int rev[N],k,mx=1;
    comn operator+(comn x,comn y){
        return {x.a+y.a,x.b+y.b};
    }comn operator-(comn x,comn y){
        return {x.a-y.a,x.b-y.b};
    }comn operator*(comn x,comn y){
        return {x.a*y.a-x.b*y.b,x.b*y.a+x.a*y.b};
    }void operator+=(comn &x,comn y){x=x+y;}
    void operator-=(comn &x,comn y){x=x-y;}
    void operator*=(comn &x,comn y){x=x*y;}
    void init(int n){
        k=0,mx=1;
        while(mx<=n) mx*=2,k++;
        for(int i=0;i<mx;i++)
            rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));
    }void fft(dft &a,int n,int fl){
        for(int i=0;i<n;i++)
            if(i<rev[i]) swap(a.fg[i],a.fg[rev[i]]);
        comn om={cos(pi),fl*sin(pi)},w={1,0};
        for(int i=1;i<n;i*=2,om={cos(pi/i),fl*sin(pi/i)})
            for(int j=0;j<n;j+=i*2,w={1,0})
                for(int l=j;l<j+i;l++){
                    comn x=a.fg[l],y=w*a.fg[l+i];
                    a.fg[l]+=y,a.fg[l+i]=x-y,w*=om;
                }
    }void sat(dft &x,int len){
        while(x.fg.size()<len) x.fg.push_back({0,0});
    }void operator+=(dft &x,dft &y){
        sat(x,y.fg.size());
        for(int i=0;i<y.fg.size();i++) x.fg[i]+=y.fg[i];
    }void operator-=(dft &x,dft &y){
        for(int i=0;i<y.fg.size();i++) x.fg[i]-=y.fg[i];
    }void pow2(dft &x){
        int n=x.fg.size();rev[0]=0;
        init(n+n),sat(x,mx),fft(x,mx,1);
        for(int i=0;i<mx;i++) x.fg[i]*=x.fg[i];
        fft(x,mx,-1);
        for(int i=0;i<mx;i++) x.fg[i].a/=mx;
    }
}using namespace FFT;
int n,dep[N],vis[N],sz[N],num[N];
long double ans;dft sum,c,d,al;vector<int>g[N];
void dfsrt(int x,int fa,int sm,int &rt){
    sz[x]=1,num[x]=0;
    for(auto y:g[x]){
        if(y==fa||vis[y]) continue;
        dfsrt(y,x,sm,rt),sz[x]+=sz[y];
        num[x]=max(num[x],sz[y]),sz[y]=1;
    }num[x]=max(num[x],sm-sz[x]);
    if(num[x]<num[rt]) rt=x;
    if(sz[x]==sm) sz[x]=1;
}void dfssz(int x,int fa){
    for(auto y:g[x])
        if(y!=fa&&!vis[y])
            dfssz(y,x),sz[x]+=sz[y];
}void dfsp(int x,int fa,dft &id){
    id.fg[dep[x]=dep[fa]+1].a++;
    for(auto y:g[x])
        if(y!=fa&&!vis[y]) dfsp(y,x,id);
}void solve(int x,int sm){
    if(sm==1) return al.fg[0].a++,void();
    d.fg.clear(),sat(d,sm);int rt=0;
    sum.fg.clear(),sat(sum,sm),sum.fg[0].a=1;
    dfsrt(x,0,sm,rt),dfssz(rt,0),vis[rt]=1,dep[rt]=0;
    for(auto y:g[rt]) if(!vis[y]){
        c.fg.clear(),sat(c,sz[y]+2);
        dfsp(y,rt,c),sum+=c,pow2(c),d+=c;
    }pow2(sum),sum-=d,al+=sum;
    for(auto y:g[rt]) if(!vis[y]) solve(y,sz[y]);
}int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n,num[0]=1e9;
    for(int i=1,x,y;i<n;i++){
        cin>>x>>y,x++,y++;
        g[x].push_back(y);
        g[y].push_back(x);
    }solve(1,n);
    for(int i=0;i<n;i++)
        ans+=al.fg[i].a/(i+1);
    cout<<stp(4)<<ans;
    return 0;
}//fast fourier transform

标签:sz,Normal,int,题解,void,BZOJ3451,num,comn,operator
From: https://www.cnblogs.com/chang-an-22-lyh/p/18678888/bzoj3451-normal-tj

相关文章

  • PKUWC 2025 题解
    本人太菜,实在不会T3,所以只有T1,T2的题解。注:考场上只做出来了Day1T1,其他题参考了其他人的题解。Day1T1题面有\(a\)个有电的电池和\(b\)个没电的电池,每次只能选择两个电池放进手电筒,只有这两个电池全有电才能让手电筒启动。问最坏情况下最少可以让手电筒启动的尝试次......
  • THUWC2025题解
    Day1T1构造一个排列,使满足最多的形如\([l,r]\)内单调递增/减。一个简单的线段树优化DP,设状态\(f_{i,0/1}\)即可转移,\(O(n\logn)\)。T2支持往集合中加三维带权点,查询集合中没有任何一维与给出点对应维度相等的最大点权。唐题。一种暴力的想法是三维数点之类的,不太能......
  • PKUWC2025部分题解
    Day1A注意到,原题等价于构造一个\(a+b\)个点的完全图,使最大独立集\(<a\),且边数最小。很难发现,图必然被划分成\(a-1\)个完全图。据此DP或令\(a-1\)个图点数平均。CDAG上考虑暴力。设\(f_{u,i}\)表示第\(i\)轮在\(u\)是否先手必胜。转移枚举相邻点就好,\(\large......
  • [ZJOI2014] 力 题解
    容易发现:\[E_i=\sum_{j=1}^{i-1}\frac{q_j}{(i-j)^2}-\sum_{j=i+1}^n\frac{q_j}{(i-j)^2}\]不妨设\(a_i=q_i,b_i=\dfrac1{i^2}\):\[E_i=\sum_{j=1}^{i-1}a_jb_{i-j}-\sum_{j=1}^{n-i}a_jb_{j-i}\]发现前半部分就是多项式乘法,后半部分与[BZOJ2194]一致。直接FFT干过去即可......
  • 线性dp+背包问题题解
    线性dp理解递推/记忆化搜索,有很多种理解方式递归重叠子问题的记忆化搜索:像这里例如\(f[3]\)可以通过一次计算得到,保存答案,下一次直接调用即可,省去很多复杂度我们从此引出dp第一个性质:最优子结构大问题的最优解包含小问题的最优解,并且小问题的最优解可以推导出大问题的最优......
  • P1076 [NOIP2012 普及组] 寻宝 题解
    题目传送锚点在博客园食用更佳本题纯纯模拟题,甚至连大模拟都算不上。别问我是怎么知道的,问就是看那恶心的题目描述、标签和题目难度仅为黄知道的。好了,上思路。既然是大模拟,那就按照题目描述给的思路来,一层一层往上爬呗。一下是两点注意事项:输入时,可以考虑用二维数组或结构......
  • P1982 [NOIP2013 普及组] 小朋友的数字 题解
    题目传送锚点在博客园食用更佳题意有一列小朋友,他们每个人都有一个值。定义每个小朋友的特征值为祂及祂前面人值的最大子段和。又定义每个小朋友的数字为祂前面人中的一个人的特征值加本身值的最大值。。。思路把题意用人话说出来即为思路:先输入每个小朋友的值\(a_i\);再......
  • AGC008 题解
    A简要题意:花费1代价+1或取反,求把\(x\)变成\(y\)的最小代价显然的,取反最多只会用两次,且必在头尾,那么直接枚举就完了代码:#include<bits/stdc++.h>#defineintlonglong#definerep(i,l,r)for(inti=l;i<=r;i++)#defineper(i,l,r)for(inti=l;i>......
  • [BZOJ2194] 快速傅立叶之二 题解
    看名字,然后准备转化为多项式乘法。\[c_k=\sum_{i=0}^{n-k-1}a_{i+k}b_i\]将\(a\)反转,得:\[c_k=\sum_{i=0}^{n-k-1}a_{n-i-k-1}b_i\]这已经是多项式乘法的格式了,直接多项式乘法,最后对函数\(c\)的\(0\)到\(n-1\)次项倒序输出即可。时间复杂度\(O(n\logn)\)。#include......
  • Codeforces Round 997 (Div. 2) 题解(A~D 题)
    CodeforcesRound997(Div.2)题解(A~D题)A因为\(x,y<m\),所以每次必有重叠的长方形。且重叠部分长为\(m-x\),宽为\(m-y\),用总周长减去算重了的部分就行。注意处理第一个长方形的边界条件。B.FindthePermutation按照\(g_{i,j}\)的大小关系直接写cmp然后sort就......