[Gym-101234D]Forest Game
壹、题目描述
贰、题解
在期望 \(\tt DP\) 专题,已经对此题思路进行过说明了(如果要看就去这里的典例营),定义 \(\text{dis}(u, v)\) 为 \(u,v\) 之间的距离(边数),那么最后的答案就是
\[\sum_{u}\sum_v{1\over \text{dis}(u,v)+1}
\]
不难发现这个东西,两点间真正的距离是点的个数,赶脚有点难搞,但是我们可以按照边数计算,最后,有 \(i\) 条边的路径实际上对应的 \(i+1\) 个点的数量,进行这个转换之后,我们就可以直接上淀粉质了,淀粉质代码简易,思路清晰,不会真有人打边分治吧?淀粉质还是使用简易容斥,用随意匹配的方案减去单个子树自己匹配自己的方案数。
但是需要注意一个细节,由于 \(u\) 可以等于 \(v\),所以我们统计的时候应该还要算 \(0\) 条边的情况 —— 这个情况对应的就是有一个点的路径。
由于 \(\bmod=10^9+7\),我们可以 把出题人吊起来打 (MTT 常数太大了,我们不考虑那东西) 使用 \(\tt FFT\) 暴力算,最后再取模。
实际上我最开始打的是 MTT,但是常数太......
叁、参考代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
#define Endl putchar('\n')
typedef long long ll;
const int maxn=3e5+10;
const int mod=1e9+7;
const double pi=acos(-1.0);
inline int qkpow(int a, int n){
int ret=1;
for(; n>0; n>>=1, a=1ll*a*a%mod)
if(n&1) ret=1ll*ret*a%mod;
return ret;
}
struct cplx{
double x, y;
cplx(){}
cplx(const double X, const double Y): x(X), y(Y){}
inline cplx operator +(const cplx rhs) const{return cplx(x+rhs.x, y+rhs.y);}
inline cplx operator -(const cplx rhs) const{return cplx(x-rhs.x, y-rhs.y);}
inline cplx operator *(const cplx rhs) const{return cplx(x*rhs.x-y*rhs.y, x*rhs.y+y*rhs.x);}
};
// should prepare first
namespace poly{
int rev[maxn<<1], n;
inline void prepare(const int len){
for(n=1; n<len; n<<=1);
for(int i=0; i<n; ++i)
rev[i]=(rev[i>>1]>>1)|((i&1)?(n>>1):0);
}
inline void fft(cplx* f, const int opt){
for(int i=0; i<n; ++i) if(i<rev[i])
swap(f[i], f[rev[i]]);
for(int p=2; p<=n; p<<=1){
int len=p>>1;
cplx buf=cplx(cos(pi/len), opt*sin(pi/len));
for(int k=0; k<n; k+=p){
cplx w=cplx(1, 0), tmp;
for(int i=k; i<k+len; ++i, w=w*buf){
tmp=f[i+len]*w;
f[i+len]=f[i]-tmp;
f[i]=f[i]+tmp;
}
}
}
}
cplx a[maxn<<1];
inline void pow(ll* f){
for(int i=0; i<n; ++i)
a[i]=cplx(f[i], 0);
fft(a, 1);
for(int i=0; i<n; ++i)
a[i]=a[i]*a[i];
fft(a, -1);
for(int i=0; i<n; ++i)
f[i]=(ll)(a[i].x/n+0.5);
}
}
int n;
struct edge{
int to, nxt;
edge(){}
edge(const int T, const int N): to(T), nxt(N){}
}e[maxn<<1];
int tail[maxn], ecnt;
inline void add_edge(const int u, const int v){
e[ecnt]=edge(v, tail[u]); tail[u]=ecnt++;
e[ecnt]=edge(u, tail[v]); tail[v]=ecnt++;
}
inline void input(){
memset(tail, -1, sizeof tail);
scanf("%d", &n);
int u, v;
for(int i=1; i<n; ++i){
scanf("%d %d", &u, &v);
add_edge(u, v);
}
}
int used[maxn+5];
int siz[maxn+5], f[maxn+5];
void findrt(const int u, const int par, const int n, int& rt){
siz[u]=1, f[u]=0;
for(int i=tail[u], v; ~i; i=e[i].nxt)
if(!used[v=e[i].to] && v!=par){
findrt(v, u, n, rt);
f[u]=max(f[u], siz[v]);
siz[u]+=siz[v];
}
f[u]=max(f[u], n-siz[u]);
if(f[u]<f[rt]) rt=u;
}
void build(int, const int);
ll ans[maxn<<1], t1[maxn<<1], t2[maxn<<1];
int maxl=0;
void dfs(const int u, const int par, const int curl){
siz[u]=1;
maxl=max(maxl, curl);
++t2[curl];
for(int i=tail[u], v; ~i; i=e[i].nxt)
if(!used[v=e[i].to] && v!=par)
dfs(v, u, curl+1), siz[u]+=siz[v];
}
void calc(const int u){
int len=0;
siz[u]=1;
++t1[0];
for(int i=tail[u], v; ~i; i=e[i].nxt)
if(!used[v=e[i].to]){
maxl=0;
dfs(v, u, 1), siz[u]+=siz[v];
len=max(len, maxl);
for(int i=0; i<=maxl; ++i)
t1[i]+=t2[i];
poly::prepare((maxl+1)<<1);
poly::pow(t2);
for(int i=0; i<=(maxl<<1)+1; ++i)
ans[i]-=t2[i];
memset(t2, 0, (maxl*2+5)<<3);
}
poly::prepare((len+1)<<1);
poly::pow(t1);
for(int i=0; i<=(len<<1); ++i)
ans[i]+=t1[i];
memset(t1, 0, (len*2+5)<<3);
}
void divide(const int u){
used[u]=1; calc(u);
for(int k=tail[u], v; ~k; k=e[k].nxt)
if(!used[v=e[k].to])
build(v, siz[v]);
}
void build(int u, const int n){
f[0]=n; int rt=0;
findrt(u, 0, n, rt);
divide(rt);
}
signed main(){
input();
build(1, n);
int res=0;
for(int i=0; i<n; ++i)
res=(res+ans[i]%mod*qkpow(i+1, mod-2)%mod)%mod;
for(int i=1; i<=n; ++i)
res=1ll*res*i%mod;
printf("%d\n", res);
return 0;
}
肆、用到の小 \(\tt trick\)
期望有很多种计算方法,有用 总体/总体 的,也有考察单个情况在什么时候会对期望产生贡献,显然此题使用的是后者。
另外,路径的组合也是一种类似卷积的东西,好多东西都是卷积啊......