连在一起的幻想乡

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-4\choose 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\n",ans<0?ans+MOD:ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/RogerDTZ/p/9652280.html