(mathcal{Description})
Link.
对于简单无向图 (G=(V,E)),定义它是“优美”的,当且仅当
给定一个“优美”的简单无向图 (G),对于所有 (iin[1,n]),求有多少个 (Ssube V),(|S|=i) 且 (S) 的导出子图是完全图。
(mathcal{Solution})
直接做铁铁的 NPC,必须从“优美”的性质入手分析。
性质一,(G) 的直径不超过 (2)。由定义显然。
性质二,记 (G) 的补图为 (overline{G}),则 (G) 与 (overline{G}) 不同时连通。证明如下:
归纳,当点集 (|V|=2) 时,显然成立。若当 (|V|<n) 时命题成立,并假设当 (|V|=n) 时命题不成立:
取任意 (uin V),由于命题不成立,故在 (G) 或是 (overline{G}) 中,(u) 都至少有一条连入点集 (Vsetminus{u}) 的边。设点集 (Vsetminus{u}) 的导出子图为 (G'),补图为 (overline{G'}),由于 (|V|setminus{u}=n-1),由归纳,(G') 与 (overline{G'}) 不同时连通。
不妨设 (G') 不连通,则令其极大连通分量为 (G_1,G_2,cdots,G_k~(k>1))。由于 (u) 在 (overline{G}) 中右边,所以必然存在 (tin G_i),使得 ((u,t) otin E)。接着研究这个 (G_i),设其点集为 (V_i),并令 (S={vin V_i~|~(u,v)in E},T=V_isetminus S)。由于假设有 (G) 连通,所以 (|S|,|T|>0)。任取 (vin S),因为 (G_i) 连通,故存在 (win T),使得 ((v,w)in E),最后,再取任意 (xin G_j~(j ot=i)) 使得 ((u,x)in E)。考虑点集 ({x,u,v,w}):
- ((x,u),(u,v),(v,w)in E);
- ((x,v),(u,w),(x,w) otin E)。
故该图不合条件,假设不成立,原命题成立。
利用“不连通”带来的子问题结构,我们分别考虑原问题在原图与补图的体现:
-
对于原图,假设其由 (G_1,G_2,cdots,G_k~(k>1)) 组成,设 (F(G)) 为图 (G) 的答案 OGF,显然有
[F(G)=sum_{i=1}^kF(G_i) ] -
对于补图,假设其由 (overline{G_1},overline{G_2},cdots,overline{G_k}~(k>1)) 组成,设 (overline{F}(overline{G})) 为图 (overline{G}) 中独立集数量关于其大小的 OGF,就有
[overline{F}(overline{G})=prod_{i=1}^koverline{F}(overline{G_i}) ]而显然 (F(G)=overline{F}(overline{G})),所以我们确实可以分治求解。
但是,本题的难点在于构造分治结构,即确定当前层是按原图的连通块划分还是补图的连通块划分。一种优秀的策略是:选择 (G) 中度数最小的点 (x) 和度数最大(即 (overline{G}) 中度数最小)的点 (y),以 (x) 为源点在 (G) 中,(y) 为源点在 (overline{G}) 中分别 BFS:先扩展一层,然后交替扩展第二层(最多两层)直到能够判断某点所在连通块 是/不是 整个图。此后,利用不连通的一个点 BFS 得到的标记数组进行连通块划分,递归建图。
考虑复杂度,瓶颈在于第二层搜索,设 (x,y) 的度数大小分别是 (d_x,d_y),复杂度上界即是 (mathcal O(d_xm+d_ym)),其中 (m) 为当前图的点集大小。记被分离的连通块大小为 (s),那么这一上界必然不超过 (mathcal O(sm))。为方便复杂度分析,设留下部分大小为 (t),有 (sle t),故 (sm=s^2+st=mathcal O(st))。考虑分治树所描述的事件:初始时有 (n) 个点,每次将剩下的点分为 (x,y) 两部分,代价为 (xy),再递归处理 (x) 和 (y)。则树上每两片叶子在 LCA 处贡献复杂度一次,总复杂度 (mathcal O(n^2))。
(mathcal{Code})
/* Clearink */
#include <cstdio>
#include <vector>
#define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i )
typedef std::pair<int, int> PII;
#define fi first
#define se second
#define getv( c ) ( '0' <= c && c <= '9' ? c ^ '0' : 10 + c - 'A' )
const int MAXN = 8e3, MOD = 998244353;
int n, node;
bool e[MAXN + 5][MAXN + 5], side[MAXN * 2 + 5];
std::vector<int> son[MAXN * 2 + 5];
inline int imin( const int a, const int b ) { return a < b ? a : b; }
inline int imax( const int a, const int b ) { return a < b ? b : a; }
inline void addeq( int& a, const int b ) { ( a += b ) >= MOD && ( a -= MOD ); }
inline int mul( const long long a, const int b ) { return int( a * b % MOD ); }
inline int build( const std::vector<PII>& vec, const bool curs ) {
int o = ++node;
if ( vec.size() == 1 ) return side[o] = curs, o;
static int vis[2][MAXN + 5], que[2][MAXN + 5];
int n = int( vec.size() ), s[2] = {};
rep ( i, 1, n - 1 ) {
if ( vec[i].se < vec[s[0]].se ) s[0] = i;
if ( vec[i].se > vec[s[1]].se ) s[1] = i;
}
s[0] = vec[s[0]].fi, s[1] = vec[s[1]].fi;
vis[0][s[0]] = vis[1][s[1]] = true;
int sz[2] = {};
rep ( sd, 0, 1 ) for ( PII u: vec ) {
if ( !vis[sd][u.fi] && e[s[sd]][u.fi] ^ curs ^ sd ) {
vis[sd][que[sd][++sz[sd]] = u.fi] = true;
}
}
int ex[2] = { sz[0], sz[1] };
bool divs;
rep ( i, 0, imax( sz[0], sz[1] ) ) {
if ( i ) rep ( sd, 0, 1 ) if ( i <= sz[sd] ) {
int u = que[sd][i];
for ( PII v: vec ) {
if ( !vis[sd][v.fi] && e[u][v.fi] ^ curs ^ sd ) {
vis[sd][que[sd][++ex[sd]] = v.fi] = true;
}
}
}
if ( i == imin( sz[0], sz[1] ) ) {
if ( ex[0] == n ) divs = curs;
else if ( ex[1] == n ) divs = !curs;
else { divs = curs ^ ( sz[0] < sz[1] ); break; }
}
}
side[o] = divs;
std::vector<PII> sub[2];
if ( divs == curs ) {
for ( PII u: vec ) {
sub[!vis[1][u.fi]].push_back( { u.fi, n - u.se - 1 } );
vis[0][u.fi] = vis[1][u.fi] = false;
}
for ( PII& u: sub[0] ) for ( PII& v: sub[1] ) {
if ( e[u.fi][v.fi] ^ curs ^ 1 ) {
--u.se, --v.se;
}
}
} else {
for ( PII u: vec ) {
sub[!vis[0][u.fi]].push_back( u );
vis[0][u.fi] = vis[1][u.fi] = false;
}
}
int lc = build( sub[0], !divs ), rc = build( sub[1], !divs );
if ( side[rc] == divs ) son[o] = son[rc];
else son[o].push_back( rc );
son[o].push_back( lc );
return o;
}
int f[MAXN * 2 + 5][MAXN + 5], siz[MAXN + 5];
inline void solve( const int u ) {
static int tmp[MAXN + 5];
if ( son[u].empty() ) return void( f[u][0] = f[u][1] = siz[u] = 1 );
f[u][0] = 1;
for ( int v: son[u] ) {
solve( v );
if ( !side[u] ) { // IG merge.
rep ( i, 0, siz[u] ) rep ( j, 0, siz[v] ) {
addeq( tmp[i + j], mul( f[u][i], f[v][j] ) );
}
rep ( i, 0, siz[u] += siz[v] ) f[u][i] = tmp[i], tmp[i] = 0;
} else { // G merge.
rep ( i, 1, siz[u] = imax( siz[u], siz[v] ) ) {
addeq( f[u][i], f[v][i] );
}
}
}
}
int main() {
scanf( "%d", &n );
rep ( i, 1, n - 1 ) {
static char str[MAXN + 5];
scanf( "%s", str );
rep ( j, 0, n - i - 1 ) {
e[i][i + j + 1] = e[i + j + 1][i] =
getv( str[j >> 2] ) >> ( j & 3 ) & 1;
}
}
std::vector<PII> oriv; oriv.resize( n );
rep ( i, 1, n ) {
int d = 0;
rep ( j, 1, n ) d += e[i][j];
oriv[i - 1] = { i, d };
}
int rt = build( oriv, false );
solve( rt );
rep ( i, 1, n ) printf( "%d%c", f[rt][i], i < n ? ' ' : '
' );
return 0;
}