• 题解 Airplane Cliques (300iq的题)


    Codeforces Gym 题目链接

    正睿2020省选十连测题目链接

    题目大意

    给定一棵(n)个节点的树。定义树上两点距离为它们之间边的数量。

    我们说一对节点是友好的,当且仅当两点之间距离小于等于(x)。其中(x)是一个题目给定的常数。

    我们说一个(k)个节点的集合是友好集合,当且仅当集合中任意两个节点都是友好的。

    请对所有(k=1dots n),求出恰有(k)个节点的友好集合数量。

    数据范围:(1leq nleq 3 imes10^5), (0leq x<n)

    本题题解

    考虑给你一个节点集合(S),如何判断它是否是友好集合?暴力做法是枚举集合里的每一对点,判断它们之间距离是否小于等于(x),如果用欧拉序LCA(O(1))求距离,则这种判断方法时间复杂度(O(|S|^2))。但这看上去太暴力了。有没有更好的方法呢?

    考虑集合(S)深度最大的节点(u)(这里的“深度”,可以是以选任意一个节点为根时的深度)。我们断言:集合(S)是好的,当且仅当(S)里所有节点到(u)的距离均小于等于(x)。必要性显然。充分性可以用反证法证明:如果存在两个节点间距离大于(x),那么两点中必有一个节点到(u)的距离也大于(x),在纸上画一画就看出来了。有了这个结论,判断集合(S)是否为友好集合的时间复杂度下降为(O(|S|))。事实上,该结论还有更大的用处。

    根据这个结论,我们可以想到一个粗略的,解决本题的方法。把所有节点,按深度从小到大排序,深度相同的节点顺序可以任意(这等价于,我们求出一个“bfs序”序列),记这个序列为(b[1dots n])。我们依次加入(b)序列里每个点,那么,只需要统计,在每个节点之前加入的节点中,距离它小于等于(x)的节点有多少个。相当于,我们认为,当前节点,就是集合里深度最大的节点,而在它之前加入的节点,就是【以当前点作为【深度最大节点】的集合】里的其他节点。具体来说,我们求出一个数组(a[1dots n])

    [a[i]=sum_{j=1}^{i-1}[ ext{dis}(b[j],b[i])leq x] ]

    那么,(k)的答案就是(ans[k]=sum_{i=1}^{n}{a[i]choose k-1})。这种按(b)序列依次考虑每个节点的做法,有效地避免了对深度相同的节点重复计算的情况。因为当集合里存在好几个深度最大的节点时,我们默认选择(b)序列里排名最靠后的,作为该集合的“深度最大的节点”。

    如果暴力求出(a)序列,再暴力计算(ans),时间复杂度(O(n^2))。我们对这两部分分别考虑优化。

    第一部分 求出(a)序列

    具体来说,我们要支持两种操作:

    1. 标记一个节点。
    2. 给定节点(u=a[i]),查询所有已标记的节点中,到(u)距离小于等于(x)的节点数。

    考虑使用点分树。点分树是基于原树,重构出来的树。它有几个特点:

    • 点分树上也有(n)个节点,对应原树里每个点。
    • 点分树上的每个子树,是原树上的一个连通块。也就是点分治时,以子树的根为分治中心的连通块。
    • 点分树的深度不超过(O(log n))

    建点分树,其实就是做“点分治”的过程。每次从当前分治中心,向每个子树的分治中心连边,就得到点分树了。

    考虑【查询所有已标记的节点中,到(u)距离小于等于(x)的节点数】。枚举这个“已标记的点”到点(u)的路径在点分树上经过的深度最浅的点,也就是点分树上两者的LCA。因为点分树深度不超过(O(log n)),所以可以暴力枚举(u)在点分树上的所有祖先。记这个祖先为(v)。那么,问题转化为,查询点分树上点(v)的子树中,在原树上距离(v)小于等于(x- ext{dis}(u,v))的节点数。当然,还要减去,点分树上(v)的【包含(u)的那个儿子】的子树里,满足条件的节点数。

    我们可以对点分树上每个节点,开两个树状数组。第一个树状数组,记录点分树上它的子树内,在原树上距离它小于等于(i)的、已标记的节点数。第二个树状数组,记录点分树上它的子树内,在原树上距离【它在点分树上的父亲】小于等于(i)的、已标记的节点数。那么,标记一个节点时,我们只需要在它点分树上祖先的树状数组上单调修改。查询时,我们在祖先的树状数组上查询一个“前缀和”。

    时间复杂度(O(nlog^2n))

    还有一个注意点,每个节点的树状数组,大小显然不能直接开到(n),否则空间复杂度会变成(O(n^2))。我们在建点分树时,统计出,以当前节点(v)为分治中心的这个连通块内,所有节点到(v)的最大距离。那么(v)的第一个树状数组,大小直接开到这个最大距离即可。这样所有“第一个树状数组”的大小之和,就是最大距离之和,显然小于等于所有连通块的大小之和。根据点分治的理论,这个和不超过(O(nlog n))。对于第二个树状数组,其大小设置为,该连通块内,到点分树上父亲的最大距离。那么,对于一个节点(v),它所有儿子的“第二个树状数组”的大小之和,小于等于以(v)为分治中心的这整个连通块的大小。所以,所有节点的“第二个树状数组”大小之和也是(O(nlog n))的。因此,空间复杂度(O(nlog n))

    第二部分 根据(a)序列求出答案数组

    我们前面说过,每个(a[i]),它对每个(k)的答案的贡献是:({a[i]choose k-1})。所以,(ans[k]=sum_{i=1}^{n}{a[i]choose k-1})。不妨记:(t[k-1]=ans[k]),现在我们就是要求这个(t)数组,其中(t[k]=sum_{i=1}^{n}{a[i]choose k})。按定义直接求,不管你是先枚举(i)再枚举(k),还是先枚举(k)再枚举(i),甚至你把(a[i])序列排序搞一搞,时间复杂度都是(O(n^2))的。

    转化一下思路。发现,(a[i])的值相同,对答案的贡献就是相同的。所以先求一个桶,(c[i]=sum_{j=1}^{n}[a[j]=i])。然后就可以得到:

    [egin{align} t[k]=&sum_{i=k}^{n-1}c[i]cdot{ichoose k}\ =&sum_{i=k}^{n-1}c[i]cdotfrac{i!}{k!(i-k)!}\ =&frac{1}{k!}sum_{i=k}^{n-1}c[i]i!frac{1}{(i-k)!} end{align} ]

    (f[i]=c[i]i!), (g[i]=frac{1}{i!})。那么,式子转化为(t[k]=sum_{i=k}^{n-1}f[i]g[i-k])。这其实也是一种卷积,我们很容易将它转化为常见的FFT的形式:

    我们熟悉的卷积都是(t_k=sum_{i=0}^{k}a_ib_{k-i}),而这里的形式是(t_k=sum_{i=k}^{n}f_ig_{i-k}),考虑如何转化。注:这里为方便表述,我们令(n=n-1)

    仔细考虑卷积的本质,我们的目标是要让相乘的两个数下标之和为定值,而我们能做的事就是尝试翻转一个数列。不妨尝试翻转(f),即令(f_i=f_{n-i})

    此时(t_k=sum_{i=k}^{n}f_{n-i}g_{i-k})

    (i)换成(i-k),则(t_k=sum_{i=0}^{n-k}f_{n-k-i}g_{i})。发现(f)(g)的下标和为(n-k),刚好是一个定值。此时如果把(f)(g)卷起来,那么结果的第(n-k)项就是(t_k)了(也就是把结果序列翻转一下)。

    因此,我们做一遍FFT就能求出答案。

    本部分时间复杂度(O(nlog n))

    结合第一部分的解法,我们在(O(nlog^2n))的时间复杂度内解决了本题。

    参考代码

    //problem:ZR1277
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    const int MAXN=3e5,MAX_LOGN=19;
    int n,X;
    struct EDGE{int nxt,to;}edge[MAXN*2+5];
    int head[MAXN+5],tot;
    inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
    
    bool vis[MAXN+5];
    int f[MAXN+5],sz[MAXN+5],root,totsize;
    void get_root(int u,int fa){
    	f[u]=0;
    	sz[u]=1;
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		if(v==fa||vis[v])continue;
    		get_root(v,u);
    		f[u]=max(f[u],sz[v]);
    		sz[u]+=sz[v];
    	}
    	f[u]=max(f[u],totsize-sz[u]);
    	if(!root||f[u]<f[root])root=u;
    }
    int div_fa[MAXN+5],div_dep[MAXN+5],maxdep[MAXN+5];
    void div_dfs(int u,int fa){
    	sz[u]=1;
    	div_dep[u]=div_dep[fa]+1;
    	maxdep[u]=div_dep[u];
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		if(v==fa||vis[v])continue;
    		div_dfs(v,u);
    		sz[u]+=sz[v];
    		maxdep[u]=max(maxdep[u],maxdep[v]);
    	}
    }
    struct FenwickTree{
    	vector<int>c;
    	int size;
    	void ins(int p){
    		++p;//距离从0开始,而树状数组的下标需要从1开始
    		assert(p>=1 && p<=size);
    		for(;p<=size;p+=(p&(-p)))c[p]++;
    	}
    	int query(int p){
    		if(p<0)return 0;
    		++p;
    		p=min(p,size);
    		assert(p>=1 && p<=size);
    		int res=0;
    		for(;p;p-=(p&(-p)))res+=c[p];
    		return res;
    	}
    	void init(int _size){
    		c.resize(_size+5);
    		size=_size;
    	}
    	FenwickTree(){}
    }fwk[MAXN+5],fwk_fa[MAXN+5];
    void build_divtree(int u){
    	//建点分树
    	vis[u]=1;
    	div_dfs(u,0);
    	fwk[u].init(maxdep[u]);
    	//cout<<"build "<<u<<" "<<sz[u]<<endl;
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		if(vis[v])continue;
    		root=0;totsize=sz[v];
    		get_root(v,0);
    		div_fa[root]=u;
    		fwk_fa[root].init(maxdep[v]+5);
    		build_divtree(root);
    	}
    }
    
    int bfs_array[MAXN+5],cnt_bfn;
    void bfs(){
    	queue<int>q;
    	q.push(1);
    	memset(vis,0,sizeof(bool)*(n+1));
    	while(!q.empty()){
    		int u=q.front();
    		q.pop();
    		vis[u]=1;
    		bfs_array[++cnt_bfn]=u;
    		for(int i=head[u];i;i=edge[i].nxt){
    			int v=edge[i].to;
    			if(vis[v])continue;
    			q.push(v);
    		}
    	}
    	//for(int i=1;i<=n;++i)cout<<bfs_array[i]<<" ";cout<<endl;
    }
    
    int dep[MAXN+5],dfn[MAXN+5],arr[MAXN*2+5],st[MAXN*2+5][MAX_LOGN+1],cnt,_log2[MAXN*2+5];
    void eulerLCA_dfs(int u,int fa){
    	dep[u]=dep[fa]+1;
    	dfn[u]=++cnt;
    	arr[cnt]=u;
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		if(v==fa)continue;
    		eulerLCA_dfs(v,u);
    		arr[++cnt]=u;
    	}
    }
    int get_lca(int u,int v){
    	int l=dfn[u],r=dfn[v];
    	if(l>r)swap(l,r);
    	int k=_log2[r-l+1];
    	return dep[st[l][k]]<dep[st[r-(1<<k)+1][k]]?st[l][k]:st[r-(1<<k)+1][k];
    }
    int get_dist(int u,int v){
    	if(u==v)return 0;
    	int lca=get_lca(u,v);
    	return dep[u]+dep[v]-dep[lca]*2;
    }
    void eulerLCA_init(){
    	eulerLCA_dfs(1,0);
    	for(int i=1;i<=cnt;++i)st[i][0]=arr[i];
    	for(int j=1;j<=MAX_LOGN;++j){
    		for(int i=1;i+(1<<(j-1))<=cnt;++i){
    			st[i][j]=dep[st[i][j-1]]<dep[st[i+(1<<(j-1))][j-1]]?st[i][j-1]:st[i+(1<<(j-1))][j-1];
    		}
    	}
    	_log2[0]=-1;
    	for(int i=1;i<=n*2;++i)_log2[i]=_log2[i>>1]+1;
    	assert(_log2[n*2]<=MAX_LOGN);
    }
    
    int a[MAXN+5],c[MAXN+5];
    
    const int MOD=998244353;
    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    inline void add(int& x,int y){x=mod1(x+y);}
    inline void sub(int& x,int y){x=mod2(x-y);}
    inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
    
    int fac[MAXN+5],ifac[MAXN+5];
    inline int comb(int n,int k){
    	if(n<k)return 0;
    	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
    }
    void facinit(int lim=MAXN){
    	fac[0]=1;
    	for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
    	ifac[lim]=pow_mod(fac[lim],MOD-2);
    	for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
    }
    
    namespace Poly{
    int rev[MAXN*4+5];
    int f[MAXN*4+5],g[MAXN*4+5];
    void NTT(int *a,int n,int flag){
    	for(int i=0;i<n;++i)if(i<rev[i])swap(a[i],a[rev[i]]);
    	for(int i=1;i<n;i<<=1){
    		int T=pow_mod(3,(MOD-1)/(i<<1));
    		if(flag==-1) T=pow_mod(T,MOD-2);
    		for(int j=0;j<n;j+=(i<<1)){
    			for(int k=0,t=1;k<i;++k,t=(ll)t*T%MOD){
    				int Nx=a[j+k],Ny=(ll)a[i+j+k]*t%MOD;
    				a[j+k]=mod1(Nx+Ny);
    				a[i+j+k]=mod2(Nx-Ny);
    			}
    		}
    	}
    	if(flag==-1){
    		int invn=pow_mod(n,MOD-2);
    		for(int i=0;i<n;++i)a[i]=(ll)a[i]*invn%MOD;
    	}
    }
    void mul(int n){
    	int lim=1,ct=0;
    	while(lim<=n+n)lim<<=1,ct++;
    	for(int i=0;i<lim;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(ct-1));
    	NTT(f,lim,1);
    	NTT(g,lim,1);
    	for(int i=0;i<lim;++i)f[i]=(ll)f[i]*g[i]%MOD;
    	NTT(f,lim,-1);
    }
    }//namespace Poly
    
    
    int main() {
    	freopen("airplane.in","r",stdin);
    	freopen("airplane.out","w",stdout);
    	cin>>n>>X;
    	for(int i=1;i<n;++i){
    		int u,v;cin>>u>>v;
    		add_edge(u,v);
    		add_edge(v,u);
    	}
    	root=0;totsize=n;get_root(1,0);
    	build_divtree(root);
    	bfs();
    	eulerLCA_init();
    	
    	for(int i=1;i<=n;++i){
    		int u=bfs_array[i];
    		a[u]=fwk[u].query(X);
    		for(int curu=div_fa[u],son=u;
    		curu!=0;
    		son=curu,curu=div_fa[curu]){
    			int d=get_dist(curu,u);
    			a[u]+=fwk[curu].query(X-d)-fwk_fa[son].query(X-d);
    		}
    		
    		for(int curu=u,son=0;
    		curu!=0;
    		son=curu,curu=div_fa[curu]){
    			int d=get_dist(curu,u);
    			fwk[curu].ins(d);
    			if(curu!=u)fwk_fa[son].ins(d);
    		}
    	}
    	//for(int i=1;i<=n;++i)cout<<a[i]<<" ";cout<<endl;
    	
    	for(int i=1;i<=n;++i)c[a[i]]++;
    	facinit();
    	for(int i=0;i<n;++i)Poly::f[i]=(ll)c[i]*fac[i]%MOD;
    	reverse(Poly::f,Poly::f+n);
    	for(int i=0;i<n;++i)Poly::g[i]=ifac[i];
    	Poly::mul(n);
    	reverse(Poly::f,Poly::f+n);
    	for(int i=1;i<=n;++i){
    		int ansi=(ll)Poly::f[i-1]*ifac[i-1]%MOD;
    		cout<<ansi<<" 
    "[i==n];
    	}
    	return 0;
    }
    
  • 相关阅读:
    GJM : Unity的profiler性能分析【转载】
    GJM :Unity UI 之 UGUI 开发
    GJM:笔记记录
    GJM : UGUI 缩放循环拖动展示卡牌效果
    GJM :HTC Vive VRTK.Package 踩坑之路(一)
    GJM :Mono 介绍
    GJM :Unity3D 介绍
    GJM : C# 介绍
    GJM : 通用类型系统(Common Type System CTS)
    GJM :SQL Server中常用的SQL语句
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13026625.html
Copyright © 2020-2023  润新知