zoukankan      html  css  js  c++  java
  • CF1292F Nora's Toy Boxes详细の题解

    题面
    啊,虽然昨晚被所有人爆踩了,但是在烦了广东队长一晚上后(俟其欣悦,俯身倾耳以请)
    卒获有所闻(虽然还是有不少东西不懂,望指出)
    首先把题目条件转变一下,把(a_{i}|a_{j})视为(i)(j)连边,然后我们就构建出了一个(DAG)
    于是题目就变成了每次在(DAG)上删一个合法的点,问将每个弱连通块删的只剩2个点的方案数(毕竟题目要求了三个点中删一个点,所以最后剩两个)
    那么答案就是每个连通块内答案的乘积
    然而并没有办法直接计算删点方案数,所以考虑如何加点
    我们留意到,在每个连通块内其实只有两种点:有可能被删的点,和绝对不会被删的点(可以看做根节点)
    p1
    如图,黑色点删不了
    而红色点无论在第几层都没啥区别(针对许多dp成瘾的小伙伴,这里不需要分层dp)
    设所有黑色点为集合(S),所有红色点集合为(T)
    口胡一个状态(dp_{s,t})(t)是我们已添加的点的集合,(s)则是所有与(t)中的点有连接的,且在(S)中的点的集合,所以

    [dp_{s,t}=sum{dp_{s',t'}} ]

    但是状态太多,直接爆炸,所以考虑把(t)改成其他东西,就改成已添加的点数好了
    口胡一个方程

    [dp{s,t}=sum{dp_{s',t-1}} ]

    看似正确
    实际上这个方程会重复统计,设对于一个点,所有与他有连接且在(S)内的点的集合为(pi)
    (pi)属于(s),那么在dp过程中,就会记重,比如
    p2
    我们沿用上面(s,t)的状态解释,若(s)={1,2},(t)={4,5},当我们用这种状态,显然我们只会添加(3)
    但是新的状态才不会管你这么多,(4,5)照样有可能被添加,那么我们就记重了
    于是怎么办,若(pi)属于(s),那么设(s)下面挂着的节点个数是(cs)
    所以在(dp_{s',t})的基础上,我们新添加一个点有(cs-t)种选择
    所以总结下方程

    [if(cs>t)dp_{s,t+1}=sum{dp_{s,t}*(cs-t)} ]

    [if(cs<t)dp_{s|pi,t+1}=sum{dp_{s,t}} ]

    最后累乘的时候还要考虑一个问题,若一个序列长度为(x)另一个为(y),两个序列是可以交叉的
    所以还要乘上组合数(C_{x+y}^{x}),来统计交叉的情况,实际写下来就是(C_{sum{len}+x}^{x})
    光说可能还是很玄幻,可以看代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<algorithm>
    #define ll long long
    #define P peach//纯属好玩 
    using namespace std;
    const ll mod=1e9+7;
    ll n,m,a[401],f[1<<15][71],g[1<<15],fa[10001],in[1001],c[201][201];
    ll peach[1<<15],ans=1,hd=0;
    vector<ll>vec[101],Vec;
    
    ll g_fa(ll x){//不需要分层,于是并查集很香 
    	if(fa[x]==x)return x;
    	else return g_fa(fa[x]);
    }
    
    int main(){
    	scanf("%lld",&n);
    	for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);
    	sort(1+a,1+a+n);
    	for(ll i=1;i<=n;i++)fa[i]=i;
    	for(ll i=1;i<=n;i++){
    		for(ll j=i+1;j<=n;j++){
    			if(a[j]%a[i]==0){
    				ll t1=g_fa(i),t2=g_fa(j);
    				in[j]++;
    				if(t1!=t2)fa[t2]=t1;
    			}
    		}
    	}
    	c[0][0]=c[0][1]=c[1][1]=1;
    	for(ll i=2;i<=2*n;i++){
    		c[0][i]=1;
    		for(ll j=1;j<=i;j++)c[j][i]=(c[j-1][i-1]+c[j][i-1])%mod;
    	}
    	for(ll i=1;i<=n;i++){
    		vec[g_fa(i)].push_back(i);
    	}
    	for(ll i=1;i<=n;i++){
    		if(vec[i].size()>1){
    			Vec.clear();
    			for(ll j=0;j<vec[i].size();j++){
    				if(in[vec[i][j]]==0)Vec.push_back(vec[i][j]);
    			}
    			ll N=1<<Vec.size(),cnt=0;
    			memset(g,0,sizeof g),memset(f,0,sizeof f);
    			for(ll j=0;j<vec[i].size();j++){
    				if(in[vec[i][j]]){
    					cnt++;
    					for(ll k=0;k<Vec.size();k++){
    						if(a[vec[i][j]]%a[Vec[k]]==0)P[vec[i][j]]|=1<<k;
    					}
    					g[P[vec[i][j]]]++;
    				}
    			}
    			for(ll j=0;j<Vec.size();j++){
    				for(ll k=0;k<N;k++){
    					if(k>>j&1)g[k]+=g[k-(1<<j)];
    				}
    			}
    			f[0][0]=1;
    			for(ll j=0;j<N;j++){
    				for(ll k=0;k<=cnt;k++){
    					if(f[j][k]){
    						if(k<g[j])f[j][k+1]=(f[j][k+1]+f[j][k]*(g[j]-k)%mod)%mod;
    						for(ll p:vec[i]){
    							if(in[p]&&((P[p]&j)!=P[p])&&((P[p]&j)||j==0)){
    								f[j|P[p]][k+1]=(f[j|P[p]][k+1]+f[j][k])%mod;
    							}
    						}
    					}
    				}
    			}
    			ans=ans*f[N-1][cnt]%mod*c[cnt-1][hd+cnt-1]%mod;
    			hd+=cnt-1;
    		}
    	}
    	cout<<ans;
    }
    
  • 相关阅读:
    晶振及COMS电路
    笔记16 C# typeof() & GetType()
    笔记15 修饰符
    笔记14 数据库编程技术
    C#基础知识
    C#连接数据库
    笔记13 winform
    笔记12 export to excel (NPOI)
    笔记11 export to excel
    笔记10
  • 原文地址:https://www.cnblogs.com/caijiLYC/p/14302549.html
Copyright © 2011-2022 走看看