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