zoukankan      html  css  js  c++  java
  • LOJ 6240. 仙人掌

    我尽力写一篇比较详细的题解。。。。

    LOJ 6240. 仙人掌

    我先来给你安利一个题 [BZOJ3451]Tyvj1953 Normal (DSU/点分治+NTT/FFT)

    同样的,我们计算每一个点对对于答案的贡献

    借一下别人严谨的分析

    我们分析这个所谓可以\(O(n^3)\)实现的dp

    (下文提到路径是指经过路径上的树边和环边)

    定义\(dp[i][j]\)当前根时,\(i\)到根节点的路径上所有经过的点中有\(j\)个点在根节点前面被选的合法方案数

    记根到\(i\)的路径上经过的点个数为\(sz[i]\)

    考虑特殊情况,如果是一棵树,那么\(j\)一定为0

    考虑最后的答案,就是这些点排列的总方案中合法的部分,即\(P_{root,i}=\sum dp[i][j]\cdot \cfrac{j!(sz[i]-1-j)!}{sz[i]!}\)

    (\(sz[i]!\):这些点顺序总方案,\(j!\)在根前面选的点可以排列,\((sz[i]-1-j)!\)在根节点后面选的点可以排列)

    我们把树边看做一个二元环,就只用考虑环上的关系了

    合法的情况,就是环上点\(x,y\)两边夹的两段环(设长度为\(len1,len2\)),之中有一段上的一些点(或者就没有)被选

    我们可以枚举\(len1,len2\),乘上组合数向\(dp[v][i+j]\)转移

    rep(i,0,sz[u]) if(dp[u][i]) {
    	rep(j,0,len1) dp[v][i+j]=(dp[v][i+j]+dp[u][i]*C[len1][j])%P;
    	rep(j,1,len2) dp[v][i+j]=(dp[v][i+j]+dp[u][i]*C[len2][j])%P;
    }
    

    注意第二个循环不能到0,会算重复

    然后我们会喜提\(O(n^4)\)写法

    那么如何优化呢?

    (转移显然可以NTT优化!)

    其实我们枚举\(len1,len2\)的本质是从每一个可选点的集合里选出一部分点,最后在将它们累加起来

    事实上,并没有必要直接枚举每一个集合里选的点

    我们可以先算我们从哪些合法点集(就是上文中的\(len1,len2\))里选点,最后考虑从合法点集的集合中选点

    也就是说\(\sum..\sum C(a_i,j_k)\)=\(C(\sum a_i,j)\) (额,意思是从若干个大小为\(a_i\)的集合中的每一个任意选一些等价于从所有的\(\sum a_i\)中选一部分)

    \(f[i][j]\)表示从合法集合中选一部分,最终得到的合法点集的集合中总点数为\(j\)的方案数

    那么我们可以得到一个简单的转化:\(f[i][j] \rightarrow dp[i][k] \cdot C(j,k)\)

    考虑\(f[i][j]\)的转移

    每次我们有两个合法集合\(len1,len2\),这两个集合我们只能取一个,所以有\(f[u][i]\rightarrow f[v][i+len1],f[v][i+len2]\)

    但是还有一个问题,我们现在考虑这两个集合,会有两个集合都不选的情况,这会被算两遍

    所以我们要多一个类似容斥?

    \(-1 \cdot f[u][i] \rightarrow f[v][i]\)(这个-1是为了突出!)

    我们完成了\(n^3\)转移,但是统计答案依然要枚举\(sz[u]^2\)的时间

    rep(i,0,sz[u]) if(dp[u][i]) {
    	dp[v][i+len1]=(dp[v][i+len1]+dp[u][i])%P;
    	dp[v][i+len2]=(dp[v][i+len2]+dp[u][i])%P;
    	dp[v][i]=(dp[v][i]-dp[u][i])%P;
    }
    

    其实要不要\(dp[u][i]\)的转移其实是一样的,我们转化一下,直接跳过

    直接考虑\(Ans=\sum _0^if[u][i]\cdot C(i,j)\cdot \cfrac{j!(sz[i]-1-j)!}{sz[i]!}\)

    右边那一坨常数对于每一个\((sz[i],i)\)的元组都是一样的,可以预处理出来

    得到\(O(n^3)\)解法

    #include<bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define reg register
    typedef long long ll;
    #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
    #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
    #define Mod2(x) ((x<0)&&(x+=P))
    #define Mod1(x) ((x>=P)&&(x-=P))
    template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
    template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }
    char IO;
    template <class T=int> T rd(){
    	T s=0;
    	int f=0;
    	while(!isdigit(IO=getchar())) f|=(IO=='-');
    	do s=(s<<1)+(s<<3)+(IO^'0');
    	while(isdigit(IO=getchar()));
    	return f?-s:s;
    }
    
    const int N=810,P=998244353;
    
    int n,m;
    ll dp[N][N],Inv[N],Fac[N],C[N][N],ans;
    vector <int> G[N],E[N];
    
    int t[N],low[N],dfn,cirsz[N],cirid[N],tmp[N],cnt,sz[N];
    int stk[N],top;
    
    void dfs1(int u,int f) { // 对于当前根处理仙人掌
    	low[u]=t[u]=++dfn;
    	stk[++top]=u;
    	for(int v:G[u]) if(v!=f) {
    		if(!t[v]) {
    			dfs1(v,u);
    			cmin(low[u],low[v]);
    			if(low[v]>=t[u]) {
    				int t;
    				cnt=0;
    				do {
    					t=stk[top--];
    					tmp[++cnt]=t;
    				} while(t!=v);
    				rep(i,1,cnt) E[u].pb(tmp[i]),cirsz[tmp[i]]=cnt+1,cirid[tmp[i]]=i+1;  //处理sz
    			}
    		} else cmin(low[u],t[v]);
    	}
    }
    
    ll Sum[N][N]; // 对于每一个sz,i的常数项预处理
    
    
    void dfs2(int u) {
    	for(int v:E[u]) {
    		sz[v]=sz[u]+cirsz[v]-1;
    		int x=cirid[v]-2,y=cirsz[v]-cirid[v]; // x即len1,y即len2
    		rep(i,0,sz[v]) dp[v][i]=0;
    		rep(i,0,sz[u]) if(dp[u][i]) {
    			dp[v][i+x]=dp[v][i+x]+dp[u][i];
    			Mod1(dp[v][i+x]);
    			dp[v][i+y]=dp[v][i+y]+dp[u][i];
    			Mod1(dp[v][i+y]);
    			dp[v][i]=dp[v][i]-dp[u][i];
    			Mod2(dp[v][i]);
    		}
    		dfs2(v);
    	}
    	drep(i,sz[u],0)	if(dp[u][i]) ans=(ans+dp[u][i]*Sum[sz[u]][i])%P; // 统计答案
    }
    
    int main(){
    	//freopen("cactus.in","r",stdin);
    	n=rd(),m=rd();
    	rep(i,1,m) {
    		int u=rd(),v=rd();
    		G[u].pb(v),G[v].pb(u);
    	}
    	Inv[0]=Inv[1]=Fac[0]=Fac[1]=1;
    	rep(i,2,n) {
    		Inv[i]=(P-P/i)*Inv[P%i]%P;
    		Fac[i]=Fac[i-1]*i%P;
    	}
    	rep(i,1,n) Inv[i]=Inv[i]*Inv[i-1]%P;
    	rep(i,0,n) rep(j,C[i][0]=1,i) C[i][j]=C[i-1][j-1]+C[i-1][j],Mod1(C[i][j]);
    	rep(i,0,n) rep(j,0,i) {
    		rep(k,0,j) Sum[i][j]=(Sum[i][j]+Fac[k]*Fac[i-k]%P*C[j][k])%P;
    		Sum[i][j]=(Sum[i][j]%P*Inv[i+1])%P;
    	}
    	rep(i,1,n) {
    		dfn=0;
    		rep(j,1,n) t[j]=0,E[j].clear();
    		dfs1(i,0);
    		sz[i]=0,dp[i][0]=1;
    		dfs2(i);
    	}
    	ans=(ans%P+P)%P;
    	printf("%lld\n",ans);
    }
    
  • 相关阅读:
    HTTP协议详解
    【VC++开发实战】迅雷晒密及批量查询流量程序
    C/C++中指针和引用之相关问题研究
    C++类中拷贝构造函数详解
    构造函数为什么不能是虚函数
    High一下!
    文件搜索神器everything 你不知道的技巧总结
    不要被C++“自动生成”所蒙骗
    对象的传值与返回
    深入浅出Node.js (3)
  • 原文地址:https://www.cnblogs.com/chasedeath/p/12067668.html
Copyright © 2011-2022 走看看