• Solution -「UR #21」「UOJ #632」挑战最大团


    (mathcal{Description})

      Link.

      对于简单无向图 (G=(V,E)),定义它是“优美”的,当且仅当

    [forall{a,b,c,d}sube V,((a,b),(b,c),(c,d)in E)Rightarrow(a,c)in Elor(b,d)in Elor(a,d)in 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;
    }
    
  • 相关阅读:
    R语言自然语言处理:关键词提取与文本摘要(TextRank)
    webapi文档描述Swagger使用
    EasyUI EasyUI combogrid 组合框使用
    关于winfrom 上传图片到服务方式
    关于NPOIExcel导出excel
    PJzhang:CVE-2019-14287 sudo权限绕过漏洞复现
    青魔法Python(持续更新)
    python如何进行内存管理
    python是如何进行内存管理的
    寒假第二次作业
  • 原文地址:https://www.cnblogs.com/rainybunny/p/14828645.html
Copyright © 2020-2023  润新知