FZOJ192. 「2019冬令营提高组」吃(点分治,NTT,概率与期望)

题目大意:
n n n 个点的树(基环树)
每次随机选取一个点进行操作,删去该点并将答案加上这个点属于的联通块的大小
问答案的期望
n ≤ 1 0 5 , n − 1 ≤ m ≤ n n\le 10^5,n-1\le m\le n n105,n1mn


先考虑树怎么做,显然删去点 x x x y y y x x x 联通的概率为 1 d i s ( x , y ) \frac{1}{dis(x,y)} dis(x,y)1 ,其中, d i s ( x , y ) dis(x,y) dis(x,y)表示 x x x y y y 的路径上的点的个数

显然可以点分治 + N T T +NTT +NTT

考虑基环树,如果路径不经过环显然不影响。

考虑经过环的路径,设 ( a → b ) (a\rightarrow b) (ab)的路径交环于 c , d c,d c,d
z = d i s ( a , c ) + d i s ( b , d ) z=dis(a,c)+dis(b,d) z=dis(a,c)+dis(b,d) , x , y x,y x,y 分别为 c , d c,d c,d 之间的两条路径的长度
那么容斥一下就可以得到概率 1 x + z + 1 y + z − 1 x + y + z \frac{1}{x+z}+\frac{1}{y+z}-\frac{1}{x+y+z} x+z1+y+z1x+y+z1

可以发现 x + z x+z x+z y + z y+z y+z 分别为两个不同的 d i s ( a , b ) dis(a,b) dis(a,b)
考虑删去一掉边再点分治 + N T T +NTT +NTT,漏掉的就是过环的另一个 d i s ( a , b ) dis(a,b) dis(a,b) 和第三项

漏掉的我们可以把环上所有子树根据距删去的边进行差分后直接 N T T NTT NTT,此时子树内会算重,我们再对子树点分治 + N T T +NTT +NTT

第三项只要把所有子树的信息合起来做一次 N T T NTT NTT再减去一个子树的贡献即可

代码:

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define ll long long
#define file(x) memset(x,0,sizeof(x))
#define pb push_back
#define SZ(x) ((int)(x.size()))
namespace io {
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	// getchar
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	// print the remaining part
	inline void flush () {
		fwrite (obuf, 1, oS - obuf, stdout);
		oS = obuf;
	}
	// putchar
	inline void putc (char x) {
		*oS ++ = x;
		if (oS == oT) flush ();
	}
	// input a signed integer
	template <class I>
	inline void gi (I &x) {
		for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;
	}
	// print a signed integer
	template <class I>
	inline void print (I &x) {
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;
		while (x) qu[++ qr] = x % 10 + '0',  x /= 10;
		while (qr) putc (qu[qr --]);
	}
	//no need to call flush at the end manually!
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io :: gi;
using io :: putc;
using io :: print;
const int p = 998244353;
inline int calc(int a,int b){return (a+b)%p;}
inline int del(int a,int b){return (a-b+p)%p;}
inline int mul(int a,int b){return 1ll*a*b%p;}
inline int max(int a,int b){return a>b?a:b;}
inline int ksm(int a,int x){int now = 1;for(;x;x>>=1,a=1ll*a*a%p) if(x&1) now = 1ll*now*a%p; return now;}
int n,m,u,v,tp;
int linkk[101000],t,du[101000],son[101000];
struct node{int n,y;}e[201000];
int inv[1001000],g[101000];
int tmp[101000],tot,dep[101000];
int A[801000],B[801000];
int mx_dep[101000];
vector<int>in[101000];
namespace NTT{
    const int G = 3;
	int r[801000],w[801000];
	void ntt(int *a,int f,int flen){
		w[0] = 1; rep(i,0,flen-1) r[i] = (r[i>>1]>>1) | ((i&1)?flen/2:0);
		rep(i,0,flen-1) if(i < r[i]) swap(a[i],a[r[i]]);
		for(int len = 2;len <= flen;len <<= 1){
			int wn = ksm(G,(p-1)/len); if(f == -1) wn = ksm(wn,p-2);
			rep(i,1,len-1) w[i] = mul(w[i-1],wn);
			for(int st = 0;st < flen;st += len)
			    rep(i,0,(len>>1)-1){
			    	int x = a[st+i],y = mul(a[st+(len>>1)+i],w[i]);
			    	a[st+i] = calc(x,y);    a[st+(len>>1)+i] = del(x,y);
				}
		}
		if(f == -1){int inv = ksm(flen,p-2);rep(i,0,flen-1) a[i] = mul(a[i],inv);}
	}
	void Mul(int *a,int n,int *b,int m){
		int flen = 1; while(flen < n+m-1) flen <<= 1;
		rep(i,n,flen-1) a[i] = 0; rep(i,m,flen-1) b[i] = 0;
		ntt(a,1,flen); ntt(b,1,flen); rep(i,0,flen-1) a[i] = mul(a[i],b[i]);
		ntt(a,-1,flen);
	}
	void Mul2(int *a,int n){
		int flen = 1; while(flen < 2*n-1) flen <<= 1;
		rep(i,n,flen-1) a[i] = 0; ntt(a,1,flen); rep(i,0,flen-1) a[i] = mul(a[i],a[i]); ntt(a,-1,flen);
	}
}using namespace NTT;
namespace cir{
	queue<int>q;
	bool vis[101000];
	void find_circle(){
		rep(i,1,n) if(du[i] == 1) vis[i] = true,q.push(i);
		while(!q.empty()){
			int x = q.front();q.pop();
			for(int i = linkk[x];i;i = e[i].n) if(!vis[e[i].y]){ du[e[i].y]--;if(du[e[i].y] == 1) q.push(e[i].y),vis[e[i].y] = true; }
		}
		rep(i,1,n) if(!vis[i]) {tmp[++tot] = i;break;}
		int x = tmp[tot]; for(int i = linkk[x];i;i = e[i].n) if(!vis[e[i].y]) {x = e[i].y;break;}
		while(x != tmp[1]) {tmp[++tot] = x;for(int i = linkk[x];i;i = e[i].n) if(!vis[e[i].y] && e[i].y != tmp[tot-1]){x = e[i].y;break;}}
		u = tmp[1];v = tmp[tot];
	}
}using namespace cir;
namespace work{
	int f[101000],sz[101000],rt,now_size;
	int ans[101000],a[401000],b[401000],mx,tot_mx;
	bool inq[101000],ok;
	void get_rt(int x,int fa){
		f[x] = 0;sz[x] = 1;
		for(int i = linkk[x];i;i = e[i].n) if(!inq[e[i].y] && e[i].y != fa) get_rt(e[i].y,x),f[x] = max(f[x],sz[e[i].y]),sz[x] += sz[e[i].y];
		f[x] = max(f[x],now_size-sz[x]); if(f[x] < f[rt]) rt = x;
	}
	void get_dis(int x,int fa,int v){a[v]++; mx=max(mx,v); for(int i = linkk[x];i;i = e[i].n) if(!inq[e[i].y] && e[i].y != fa) get_dis(e[i].y,x,v+1);}
	void tr_calc(int x,int v,int f){
		mx = 0;get_dis(x,0,f); 
		NTT::Mul2(a,mx+1); 
		rep(i,0,2*mx) ans[i] = calc(ans[i],mul(v,a[i])); 
		rep(i,0,2*mx) a[i] = 0; tot_mx = max(tot_mx,2*mx);
	}
	void solve(int x){
		inq[x] = true; tr_calc(rt,1,0);
		for(int i = linkk[x];i;i = e[i].n) if(!inq[e[i].y]) tr_calc(e[i].y,-1,1) , now_size = sz[e[i].y] , rt = 0 , get_rt(e[i].y,x) , solve(rt);
	}
	void doit(int x,int sz){
		if(x == 1 && sz == 2) ok = true;
		rt = 0;now_size = sz;f[0] = 2*n;
		get_rt(x,0);solve(rt);
		if(x == 1 && sz == 2) ok = false;
	}
	void again(){
		rep(i,0,tot_mx) ans[i] = 0;
		tot_mx = 0;
	}
}using namespace work;
void insert(int x,int y){
	e[++t].y = y;e[t].n = linkk[x];linkk[x] = t;du[x]++;
	e[++t].y = x;e[t].n = linkk[y];linkk[y] = t;du[y]++;
}
void dele(int x,int y){if(e[linkk[x]].y == y) {linkk[x] = e[linkk[x]].n;return;}for(int i = linkk[x];i;i = e[i].n) if(e[e[i].n].y == y) {e[i].n = e[e[i].n].n;return;}}
void get_sz(int x,int fa){
    dep[x] = dep[fa] + 1; if(dep[x] > mx_dep[tp]) mx_dep[tp]++,in[tp].pb(0); in[tp][dep[x]]++; sz[x] = 1;
	for(int i = linkk[x];i;i = e[i].n) if(!inq[e[i].y] && e[i].y != fa) get_sz(e[i].y,x),sz[x] += sz[e[i].y];
}
int fuck;
int f1(){
	int now = 0,n = 0,m = 0;
	rep(i,1,tot) {rep(j,0,SZ(in[i])-1) A[j+i-1] = calc(A[i+j-1],in[i][j]);n = max(n,i+SZ(in[i])-1);}
	rep(i,1,tot) {rep(j,0,SZ(in[i])-1) B[j+tot-i] = calc(B[j+tot-i],in[i][j]);m = max(m,tot-i+SZ(in[i]));}
	NTT::Mul(A,n,B,m);
	rep(i,0,n+m-2) now = calc(now,mul(inv[i+2],A[i])),A[i] = 0;
	return now;
}
int f2(){
	int now = 0,n = 0;
	rep(i,1,tot){
		rep(j,0,SZ(in[i])-1) A[j] = calc(A[j],in[i][j]);
		n = SZ(in[i]); Mul2(A,n);
		rep(i,0,2*n-2) {
			now = del(now,mul(inv[2],mul(inv[i+tot],A[i])));
			now = calc(now,mul(inv[i+1+tot],A[i])),A[i] = 0;
		}
	}
	now = del(now,mul(fuck,inv[2]));
	return now;
}
int f3(){
	int now = 0,n = 0;
	rep(i,1,tot) rep(j,0,SZ(in[i])-1) A[j] = calc(A[j],in[i][j]),n = max(n,SZ(in[i]));
	Mul2(A,n); rep(i,0,2*n-2) now = calc(now,mul(inv[i+tot],A[i]));
	return now;
}
void Solve(){
	int ans = 0,now = 0;
	rep(i,1,200000) inv[i] = ksm(i,p-2);
	rep(i,0,work::tot_mx) ans = calc(ans,mul(inv[i+1],work::ans[i])),now = calc(now,mul(inv[i+1+tot],work::ans[i]));
	if(m == n-1){printf("%d\n",ans);exit(0);}
	file(work::inq); rep(i,1,tot) work::inq[tmp[i]] = true;dep[0] = -1;
	rep(i,1,tot){
		work::inq[tmp[i]] = false;mx_dep[i] = -1; tp = i; get_sz(tmp[i],0); work::again(); work::doit(tmp[i],sz[tmp[i]]);
		rep(i,0,work::tot_mx) now = del(now,mul(work::ans[i],inv[i+1+tot]));
	}
	now = mul(now,inv[2]);now = del(f1(),now);now = del(now,f2());
	now = mul(now,2);ans = calc(ans,now);ans = del(ans,f3());
	printf("%d\n",ans);
}
int main(){
	freopen("eat.in","r",stdin);
	freopen("eat.out","w",stdout);
	gi(n);gi(m);int x,y;
	rep(i,1,m) gi(x),gi(y),insert(x,y); if(m == n-1) goto loop;
	cir::find_circle(); dele(tmp[1],tmp[tot]); dele(tmp[tot],tmp[1]);
	loop:; work::doit(1,n);
	Solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值