CF809E Surprise me 题解
大力推式子+莫比乌斯反演+虚树
Statement
给定一棵 个节点的树,每个点有一个权值 ,保证 是一个 的排列。
求 ,对 取模。
Solution
据说里面全是套路,但是我啥都第一次
首先忽略 ,探究一下 咋办
看到 ,直接提出去算;
设
看到 的形式,直接莫反,设
知道 ,继续算
发现有点拆不动,但其实已经不用拆了,注意到题目中 是一个排列,所以 的只有 个(级数求和)
我们可以考虑直接枚举 ,然后把对应的 提出来算贡献,也就是算
把这个式子拆成三个式子,容易发现形如 的式子可以直接扫一遍得到 和 乘起来即可,设这个乘积叫
考虑咋算
不妨提一棵虚树出来,然后在虚树上做一个简单树形 DP 即可
那么 ,算 是 的,建虚树还有一个
然后 可以反演得到 ,结束
所以总复杂度 ,有一点码量,虚树清零的时候小心
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
const int mod = 1e9+7;
char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
int s=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
return s*w;
}
void inc(int &a,int b){a=a>=mod-b?a-mod+b:a+b;}
void dec(int &a,int b){a=a>=b?a-b:a+mod-b;}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod,b>>=1;
}
return res;
}
int w[N],rev[N],g[N],f[N];
int n,elen,ans;
bool vis[N];
struct Real_Tree{
vector<int>Edge[N];
int siz[N],son[N],top[N],f[N],dep[N],dfn[N];
int tim;
void dfs1(int u){
for(auto v:Edge[u])if(v^f[u])
dep[v]=dep[f[v]=u]+(siz[v]=1),dfs1(v),siz[u]+=siz[v],
(siz[v]>siz[son[u]]&&(son[u]=v,1));
}
void dfs2(int u,int tp){
top[u]=tp,dfn[u]=++tim;
if(son[u])dfs2(son[u],tp);
for(auto v:Edge[u])if(v^f[u]&&v^son[u])dfs2(v,v);
}
int lca(int u,int v){
while(top[u]^top[v])
dep[top[u]]<dep[top[v]]?v=f[top[v]]:u=f[top[u]];
return dep[u]<dep[v]?u:v;
}
void build(){
for(int i=1,u,v;i<n;++i)
u=read(),v=read(),
Edge[u].push_back(v),
Edge[v].push_back(u);
dfs1(siz[1]=dep[1]=1),dfs2(1,1);
}
}rt;
struct Math_Fuction{
int phi[N],mu[N],prime[N];
bool vis[N];
int cnt;
void build(){
phi[1]=mu[1]=1;
for(int i=2;i<N;++i){
if(!vis[i])phi[i]=i-1,mu[i]=-1,prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<N;++j){
vis[i*prime[j]]=true;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*phi[prime[j]];
mu[i*prime[j]]=-mu[i];
}
}
}
}mf;
struct Virual_Tree{
struct Edge{int nex,to;}edge[N<<1];
int head[N],spc[N],dp[N];
int elen=1,num,res;
void addedge(int u,int v){
edge[++elen]=(Edge){head[u],v},head[u]=elen;
edge[++elen]=(Edge){head[v],u},head[v]=elen;
}
void reset(){
for(int i=1;i<=num;++i)
head[spc[i]]=vis[spc[i]]=0;
elen=1,num=0;
}
void build(){
sort(spc+1,spc+1+num,[](int x,int y){
return rt.dfn[x]<rt.dfn[y];});
for(int i=2;i<=num;++i){
int l=rt.lca(spc[i],spc[i-1]);
if(l!=spc[i]&&l!=spc[i-1])spc[++num]=l;
}
sort(spc+1,spc+1+num);
num=unique(spc+1,spc+1+num)-spc-1;
sort(spc+1,spc+1+num,[](int x,int y){
return rt.dfn[x]<rt.dfn[y];});
for(int i=2;i<=num;++i)
addedge(rt.lca(spc[i],spc[i-1]),spc[i]);
}
void dfs(int u,int fath){
if(vis[u])inc(res,2ll*mf.phi[w[u]]*mf.phi[w[u]]*rt.dep[u]%mod),dp[u]=mf.phi[w[u]];
for(int e=head[u],v;v=edge[e].to,e;e=edge[e].nex)if(v^fath)
dfs(v,u),inc(res,4ll*dp[u]*dp[v]%mod*rt.dep[u]%mod),inc(dp[u],dp[v]),dp[v]=0;
}
int calc(){
res=0,dfs(spc[1],spc[1]),dp[spc[1]]=0;
return res;
}
}vt;
void calc(int x){
int sum1=0,sum2=0;
for(int i=x;i<=n;i+=x)
vt.spc[++vt.num]=rev[i],vis[rev[i]]=1,
inc(sum1,1ll*rt.dep[rev[i]]*mf.phi[i]%mod),
inc(sum2,mf.phi[i]);
vt.build();
sum1=1ll*sum1*sum2%mod;
g[x]=(2ll*sum1-vt.calc()+mod)%mod;
vt.reset();
}
signed main(){
n=read();
for(int i=1;i<=n;++i)
w[i]=read(),rev[w[i]]=i;
rt.build(),mf.build();
for(int i=1;i<=n/2;++i)
calc(i);
for(int i=1;i<=n;++i)
for(int j=i;j<=n;j+=i)
inc(f[i],(g[j]*mf.mu[j/i]+mod)%mod);
for(int i=1;i<=n;++i)
inc(ans,1ll*i*ksm(mf.phi[i],mod-2)%mod*f[i]%mod);
ans=1ll*ans*ksm(n,mod-2)%mod*ksm(n-1,mod-2)%mod;
printf("%d\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通