描述
给定一个n个点,m条边的图, 考虑这个图的边集的子集(一共有2m个).
对于每一个子集, 如果其导出的子图的每个连通分量都有欧拉回路, 那么我们就把答案加上这个子图的边数的平方. 求答案对109+7取模的值.
输入
第一行两个数n和m. 点从1到n标号.
接下来m行,每行两个数a和b,表示a和b之间有一条路, a不等于b.
可能有重边.
n<=60, m<=100
思路:
1.如果考虑不是统计平方而是个数的话,直接高斯消元可做,具体增广矩阵的构造是,每一行代表一个节点从而建立一个方程,共有m项,每一项代表第i条边选还是不选,根据欧拉定理,最后所有项异或起来为0,这样直接消元统计方案便可。
2.现在是平方,那么我们可以这样做:枚举两条边必选解的情况,然后会发现每个合法子图被统计了 C(n, 2)次,乘以2会发现统计了n^2-n次,还差n次,同样的,我们再枚举一条边必选的情况,这样正好是n次,一加,正好每个合法子图被统计n^2次。
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <algorithm>
#define F(p) (p.first)
#define S(p) (p.second)
const int maxn = 107;
const int mod = 1e9 + 7;
typedef std::pair<int, int> pii;
pii edges[maxn];
int A[maxn][maxn];
int guss(int n, int m)
{
int i = 0, now = 0, ans = 1;
while(i < n && now < m) {
int row = i;
for(int j = i; j < n; j ++) {
if(A[j][now]) { row = j; break; }
}
if(A[row][now] == 0) { now ++; continue; }
for(int j = now; j <= m; j ++) std::swap(A[i][j], A[row][j]);
for(int j = i + 1; j < n; j ++) {
if(A[j][now]) for(int k = now; k <= m; k ++) { A[j][k] ^= A[i][k]; }
}
i ++, now ++;
}
for(int j = i; j < n; j ++) if(A[j][m]) return 0;
for(int j = 0; j < m - i; j ++) ans = ans * 2 % mod;
return ans;
}
int cal(int s, int t, int n, int m)
{
int k = 0;
memset(A, 0, sizeof(A));
for(int j = 1; j <= m; j ++) {
if(j == s || j == t) continue;
int u = F(edges[j]);
int v = S(edges[j]);
A[u][k] ^= 1;
A[v][k] ^= 1;
k ++;
}
if(s != -1) A[F(edges[s])][k] ^= 1, A[S(edges[s])][k] ^= 1;
if(t != -1) A[F(edges[t])][k] ^= 1, A[S(edges[t])][k] ^= 1;
return guss(n, k) % mod;
}
int main()
{
std::ios::sync_with_stdio(false);
int n, m, k = 0, ans = 0;
std::cin >> n >> m;
for(int i = 1; i <= m; i ++) {
std::cin >> F(edges[i]) >> S(edges[i]);
}
for(int i = 1; i <= m; i ++) {
for(int j = i + 1; j <= m; j ++) {
ans = (ans + 2 * cal(i, j, n, m) % mod) % mod;
}
}
for(int j = 1; j <= m; j ++) {
ans = (ans + cal(j, -1, n, m)) % mod;
}
std::cout << ans << "\n";
}