[BZOJ2169] 连边
问题描述
有N个点(编号1到N)组成的无向图,已经为你连了M条边。请你再连K条边,使得所有的点的度数都是偶数。求有多少种连的方法。要求你连的K条边中不能有重边,但和已经连好的边可以重。不允许自环的存在。求连边的方法数。我们只关心它模10007的余数。
输入格式
输入的第一行有三个自然数,分别表示点数N,已经连好的边数M,和你要连的边数K。保证K≤N(N-1)/2 接下来M行每行两个整数x,y,描述了一条连接x和y的边。 30%的数据满足: N≤200 100%的数据满足: N≤1000,M≤N,K≤1000,K≤N(N-1)/2
输出格式
输出一个整数,表示连边的方法数模10007的余数
样例输入
5 1 4
1 2
样例输出
13
说明
对于20%的数据, 4≤N≤100。对于40%的数据, 4≤N≤5000。对于100%的数据,4≤N≤50000 1≤M≤10 M≤N 所有出现的整数均不超过32位含符号整数。 时间限制:1s
链接
解析
考虑使用动态规划的方法来实现。设\(f[i][j]\)表示当前加了\(i\)条边,还有\(j\)个点的度数为奇数。那么,考虑如何转移。每连一条边,我们就要在\(n\)个点中选择两个点。我们可以选择两个奇点,使奇点数减2。也可以选择偶点,使奇点数加2,也可以选择一奇一偶,则奇点个数不变。所以,我们有如下状态转移方程:
\[f[i][j]=f[i-1][j-2]\times C_{n-j+2}^2+f[i-1][j+2]\times C_{j+2}^2+f[i-1][j]\times C_j^2\times C_{n-j+1}^2
\]
但是,这样计数是会有重复的。一是连的边中会有重边,所以,我们需要减去上一层转移中有重边的方案。我们可以强制有两条边连在了同两个点上,其余边都不重复,即
\[f[i][j]=f[i][j]-f[i-2][j]\times (C_n^2-i+2)
\]
另外一个是连边没有顺序问题,所以
\[f[i][j]=f[i][j]/i
\]
代码
#include <iostream>
#include <cstdio>
#define N 1002
using namespace std;
const int mod=10007;
int n,m,k,i,j,d[N],f[N][N],C[N][N];
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
int poww(int a,int b)
{
int ans=1,base=a;
while(b){
if(b&1) ans=ans*base%mod;
base=base*base%mod;
b>>=1;
}
return ans;
}
int main()
{
n=read();m=read();k=read();
for(i=1;i<=m;i++){
int u=read(),v=read();
d[u]++;d[v]++;
}
C[0][0]=1;
for(i=1;i<=n;i++){
C[i][0]=1;
for(j=1;j<=n;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
int cnt=0;
for(i=1;i<=n;i++){
if(d[i]%2) cnt++;
}
f[0][cnt]=1;
for(i=1;i<=k;i++){
for(j=0;j<=n;j++){
if(j>=2) f[i][j]=(f[i][j]+f[i-1][j-2]*C[n-j+2][2]%mod)%mod;
if(j<=n-2) f[i][j]=(f[i][j]+f[i-1][j+2]*C[j+2][2]%mod)%mod;
f[i][j]=(f[i][j]+f[i-1][j]*C[j][1]%mod*C[n-j][1]%mod)%mod;
if(i>=2) f[i][j]=(f[i][j]-f[i-2][j]*(C[n][2]-i+2+mod)%mod+mod)%mod;
f[i][j]=f[i][j]*poww(i,mod-2)%mod;
}
}
printf("%d\n",f[k][0]);
return 0;
}