連在一塊兒的幻想鄉

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} \]
  ide

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;
}
相關文章
相關標籤/搜索