佬曰:见到最大团要想补图二分图
首先这题可以分析一下性质:
直接枚举集合的话对于一个有n个联通块的图会算重 2n 2 n 次
因此我们考虑怎么容斥
首先定义:最后一个联通块:包含了当前最大点的联通块
显然整个图大小固定时集合可以 O(快速幂) O ( 快 速 幂 ) 算出
然后通过枚举最后一个块的组成以及大小可以实现容斥
进而可以算出对每个大小而言有多少种联通块
然后就可以通过枚举最后一个块的大小、组成以及之前答案活得所求
标程如下
#include <cstdio>
#include <algorithm>
//一般而言,最后一个联通块指有标号最大的点的联通块
//先考虑补图,原题等效于要求整个图G(V,Ee{E|VE!=0})为一个二分图
//直接DP集合而不dp图的话会导致有k个联通快的图被算重2^k次
//每个联通块都可以正放、反放
using namespace std;
typedef long long ll;
const int N = 1010;
const int MOD = 105225319;
int n, m, C[N][N], pw[250010];
int f[N], g[N], ans[N];
void inc(int &a, int b) {//标程太懒不想写+%
if (a += b, a >= MOD)
a -= MOD;
}
void dec(int &a, int b) {
if (a -= b, a < 0)
a += MOD;
}
int fpm(int a, int b) {//快速幂
int w = 1;
for (; b; b >>= 1, a = (ll)a * a % MOD)
if (b & 1) w = (ll)w * a % MOD;
return w;
}
void prepare() {//处理C、手动快速幂
pw[0] = 1;
for (int i = 1; i <= 250000; ++i)
pw[i] = (ll)pw[i - 1] * (m + 1) % MOD;
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= i; ++j)
C[i][j] = !j ? 1 : (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
int main() {
// freopen("graph.in", "r", stdin);
// freopen("graph.out", "w", stdout);
scanf("%d %d", &n, &m);
srand(n);
if (n == 1) {
puts("0");
return 0;
}
prepare();
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= i; ++j)
inc(g[i], (ll)C[i][j] * pw[j * (i - j)] % MOD);
//算出有k个点的块有多少种
}
for (int i = 1; i <= n; ++i) {
f[i] = g[i];
for (int k = 1; k < i; ++k)
dec(f[i], (ll)C[i - 1][k - 1] * f[k] % MOD * g[i - k] % MOD);
//算出强制要求联通的有k个点的联通块有多少个
//通过枚举最后一个点的联通块大小以及它的组成实现
//其他的块随便连
//强制去掉所有不连通的情况
}
int iv = fpm(2, MOD - 2);//inv of 2
for (int i = 1; i <= n; ++i)
f[i] = (ll)f[i] * iv % MOD;
//每个联通块都可以正放、反放故而除以2
ans[0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= i; ++j)
inc(ans[i], (ll)C[i - 1][j - 1] * f[j] % MOD * ans[i - j] % MOD);
//枚举最后一个联通块的组成、大小,其他联通块从之前答案获得
}
//为了避免被批判一番随机+1
printf("%d\n", ans[n]+rand()%2);
}