题意
- 给定 \(n\) 个点的带权无向完全图,点 \(i, j\) 之间的权值为 \(a_{i, j}\),权值是一个 \(1 \sim \frac{n(n-1)}{2}\) 的排列。
- 计数把原图划分成 \(k\) 个组的方案数,满足:
- 对于任意的 \((s, f), (x, y)\),其中 \(s, f, x\) 同组,\(y\) 与 \(x\) 不同组 (\(s \ne f, x \ne y\)),\(a_{s, f} < a_{x, y}\),即(对于每个组)组间边大于组内边。
- 输出一行 \(n\) 个数,对于 \(k \in [1, n]\) 求出答案,对 \(998244353\) 取模。
- \(n \le 1500\)
题解
考虑从小到大处理边。将边排序,依次在并查集上连接。
设 \(f_i\) 为某个连通块划分成 \(i\) 个组的方案数。
如果一个连通块还不是团时就已经和另一个连通块相连接,那么它不能单独成组,因为组内有比组间更大的边(还未加入)。
初始时每个点可以单独成组,因此对于每个点 \(f_1 = 1\)。连接两个连通块时直接做多项式卷积即可(不可能发生合并),加入所有边即可得到答案。
当一个连通块形成一个团时,可以单独成组,因此令 \(f_1\) 增 \(1\)。
复杂度 \(\mathcal O(n^2)\),类似树形 DP。
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;
using ll = long long;
namespace io {
#define File(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
const int SIZE = (1 << 21) + 1;
char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1;
inline char getc () {return (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++);}
inline void flush () {fwrite (obuf, 1, oS - obuf, stdout); oS = obuf;}
inline void putc (char x) {*oS ++ = x; if (oS == oT) flush ();}
template<class T>
inline void read(T &x) {
char ch; int f = 1;
x = 0;
while(isspace(ch = getc()));
if(ch == '-') ch = getc(), f = -1;
do x = x * 10 + ch - '0'; while(isdigit(ch = getc()));
x *= f;
}
template<class T, class ...Args>
inline void read(T &x, Args&... args) {read(x); read(args...);}
template<class T>
inline void write(T x) {
static char stk[128];
int top = 0;
if(x == 0) {putc('0'); return;}
if(x < 0) putc('-'), x = -x;
while(x) stk[top++] = x % 10, x /= 10;
while(top) putc(stk[--top] + '0');
}
template<class T, class ...Args>
inline void write(T x, Args... args) {write(x); putc(' '); write(args...);}
inline void space() {putc(' ');}
inline void endl() {putc('\n');}
struct _flush {~_flush() {flush();}} __flush;
};
using io::read; using io::write; using io::flush; using io::space; using io::endl; using io::getc; using io::putc;
const int M = 998244353;
inline int add(int x, int y) { return x + y >= M ? x + y - M : x + y; }
template <class... Args> inline int add(int x, int y, Args... args) { return add(add(x, y), args...); }
inline int sub(int x, int y) { return x - y < 0 ? x - y + M : x - y; }
inline int mul(int x, int y) { return 1LL * x * y % M; }
template <class... Args> inline int mul(int x, int y, Args... args) { return mul(mul(x, y), args...); }
inline void inc(int &x, int y=1) { x += y; if(x >= M) x -= M; }
inline void dec(int &x, int y=1) { x -= y; if(x < 0) x += M; }
inline int power(int x, int y) {
int res = 1;
for(; y; y>>=1, x = mul(x, x)) if(y & 1) res = mul(res, x);
return res;
}
inline int inv(int x) { return power(x, M - 2); }
using poly = vector<int>;
poly operator*(poly a, poly b) {
poly c(a.size() + b.size() + 1);
for (size_t i = 0; i < a.size(); ++i)
for (size_t j = 0; j < b.size(); ++j)
inc(c[i + j + 1], mul(a[i], b[j]));
return c;
}
const int N = 1505;
int fa[N], siz[N], edges[N];
poly f[N];
int find(int x) {
return !fa[x] ? x : fa[x] = find(fa[x]);
}
void link(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
++edges[x];
if (edges[x] == siz[x] * (siz[x] - 1) / 2)
++f[x][0];
return;
}
if (siz[x] < siz[y]) swap(x, y);
fa[y] = x;
siz[x] += siz[y];
edges[x] += edges[y] + 1;
f[x] = f[x] * f[y];
if (edges[x] == siz[x] * (siz[x] - 1) / 2)
++f[x][0];
}
struct edge {
int x, y, w;
}e[N * N / 2];
int main() {
File("g");
int n;
read(n);
fill(f + 1, f + 1 + n, poly({1}));
fill(siz + 1, siz + 1 + n, 1);
fill(edges + 1, edges + 1 + n, 0);
int ec = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
int w; read(w);
if (i < j) e[++ec] = {i, j, w};
}
sort(e + 1, e + 1 + ec, [] (edge x, edge y) {return x.w < y.w;});
for (int i = 1; i <= ec; ++i)
link(e[i].x, e[i].y);
int rt = find(1);
for (int i = 0; i < n; ++i)
write(f[rt][i]), space();
endl();
return 0;
}