zoukankan      html  css  js  c++  java
  • 连在一起的幻想乡

    Solution

      
      如果答案求的是每种情况,但输出时不直接输出而是采用加密或压缩的形式,那么情况一般有两种:
      
      1. 输出量过大。一般题目会有额外说明“由于输出较多,你只需要输出...."
      
      2. 题目本身统计时使用这种”加密方式“的形式统计会非常好做。一般题目不会额外说明,且输出量较小
      
      对于一个使用了(k)条边的联通子图,其贡献为(k^2),相当于枚举这个子图中的每两条边,问能枚举多少个有序对(自己和自己配对只算一次)
      
      放到全局,就是统计对于任意两条边,有多少联通子图包含它们
      
      首先看一下求解联通子图个数的式子:(f_n)表示(n)个有标号点的联通子图数量,(g_n)表示(n)个点的生成子图数量:

    [f_n=g_n-sum_{i=1}^{n-1}{n-1 choose i-1}f_ig_{n-i} ]

      先统计总数,再枚举1号点所在连通块大小,假设另一部分与i所在位置完全不连通,减去所有不合法情况
      
      接下来不好想了,没思路;没思路就考虑分类讨论着两条边的关系,总会有容易切入的一面
      
      1. 两条边是同一条边:相当于有两个点总是打包连通的。由于枚举每次枚举边时情况相同,所以答案乘上总边数(n choose 2)直接加进总答案。与朴素算法的差别仅仅在于:(g_n)表示(n)个点的一条边强制选时生成子图数量,(i)枚举的是这两个点所在连通块的大小,因此从2开始枚举,组合数变成(n-2 choose i-2)
      
      2. 两条边共用一个顶点:答案乘上(n*(n-1)*(n-2))贡献进总答案。考虑这三个点已经有V字的两条边,第三条边加不加都可以,有两种选择,这个选择和外界无关,是个独立系数,假设第三条边不加,最后的答案乘以2即可。然后(g_n)表示强制选3条边时生成子图数量,连通块大小从3开始枚举,组合数变成(n-3 choose i-3)
      
      3. 两条边完全独立:答案乘上({n choose 2}{n-2 choose 2})贡献进总答案。先只看这两个条边、四个点。(g_n)变成强制选2条边时的生成子图数量。此时减去的部分不仅有从(i=4)开始枚举,组合数变成({n-4 choose i-4})的情况(这是枚举两条边已经联通的情况);而且有两条边不连通的情况,(i)枚举一条边所在连通块大小,组合数变成(n-4 choose i-2),枚举一条边所在连通块大小,那么另一条边在另一个我们假定的与这个连通块完全不连通的部分,那么原来的(g_{n-i})的定义应该变成一条边必须选时的生成子图个数,而卷积的另一个(f)应该用回第一个情况时算出的(f)

    [ f_n=g_{2,n}-sum_{i=4}^{n-1}{n-4choose i-4}f_ig_{0,n-i} -sum_{i=2}^{n-2}{n-4 choose i-2}f'_ih_{1,n-i} ]

      

    Code

    #include <cstdio>
    using namespace std;
    const int N=2005;
    int n;
    int MOD;
    int c[N][N],mi[N*N],h[4][N];
    int f1[N],f2[N],f3[N];
    int fmi(int x,int y){
    	int res=1;
    	for(;y;x=1ll*x*x%MOD,y>>=1)
    		if(y&1)
    			res=1ll*res*x%MOD;
    	return res;
    }
    void init(){
    	c[0][0]=1; 
    	for(int i=1;i<=n;i++){
    		c[i][0]=1;
    		for(int j=1;j<=i;j++)
    			c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD;
    	}
    	mi[0]=1;
    	for(int i=1,up=n*n;i<=up;i++)
    		mi[i]=(mi[i-1]<<1)%MOD;
    	for(int remove=0;remove<4;remove++)
    		for(int i=1;i<=n;i++)
    			if(i*(i-1)/2>=remove) h[remove][i]=mi[i*(i-1)/2-remove];
    			else
    				h[remove][i]=0;
    }
    int calc_sameEdge(int *f){
    	f[2]=1;
    	for(int i=3;i<=n;i++){
    		f[i]=h[1][i];
    		for(int j=2;j<i;j++)
    			(f[i]-=1ll*f[j]*h[0][i-j]%MOD*c[i-2][j-2]%MOD)%=MOD;
    	}
    	return f[n];
    }
    int calc_shareNode(int *f){
    	f[3]=2; // because the third edge can be choosen or not
    	for(int i=4;i<=n;i++){
    		f[i]=(h[3][i]<<1); // remove 3 edge which have been considered (2 situation)
    		for(int j=3;j<i;j++)
    			(f[i]-=1ll*f[j]*h[0][i-j]%MOD*c[i-3][j-3]%MOD)%=MOD;
    	}
    	return f[n];
    }
    int calc_isolate(int *f,int *g){
    	f[4]=15;
    	for(int i=5;i<=n;i++){
    		f[i]=h[2][i];
    		for(int j=2;j<=i-2;j++)
    			(f[i]-=1ll*g[j]*h[1][i-j]%MOD*c[i-4][j-2]%MOD)%=MOD;
    		for(int j=4;j<i;j++)
    			(f[i]-=1ll*f[j]*h[0][i-j]%MOD*c[i-4][j-4]%MOD)%=MOD;
    	}
    	return f[n];
    }
    int main(){
    	freopen("input.in","r",stdin);
    	scanf("%d%d",&n,&MOD);
    	init();
    	int ans=0;
    	if(n>=2)
    		(ans+=1ll*c[n][2]*calc_sameEdge(f1)%MOD)%=MOD;
    	if(n>=3)
    		(ans+=1ll*n*(n-1)%MOD*(n-2)%MOD*calc_shareNode(f2)%MOD)%=MOD;
    	if(n>=4)
    		(ans+=1ll*c[n][2]*c[n-2][2]%MOD*calc_isolate(f3,f1)%MOD)%=MOD;
    	printf("%d
    ",ans<0?ans+MOD:ans);
    	return 0;
    }
    
  • 相关阅读:
    【CSS】 布局之圣杯布局
    PHP函数
    spry菜单栏(二)
    正则表达式补充
    练习用php做表格
    PHP入门
    MySQL常用函数
    数据库习题
    总结
    Navicat
  • 原文地址:https://www.cnblogs.com/RogerDTZ/p/9652280.html
Copyright © 2011-2022 走看看