若是答案求的是每种状况,但输出时不直接输出而是采用加密或压缩的形式,那么状况通常有两种:
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
#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; }