1. 程式人生 > 實用技巧 >Solution -「CF 599E」Sandy and Nuts

Solution -「CF 599E」Sandy and Nuts

\(\mathcal{Description}\)

  Link.

  指定一棵大小為 \(n\),以 \(1\) 為根的有根樹的 \(m\) 對鄰接關係與 \(q\)\(\text{LCA}\) 關係,求合法樹的個數。

  \(0\le m<n\le13\)\(q\le100\)

\(\mathcal{Solution}\)

  巧妙的狀壓 owo。不考慮限制,自然地有狀態 \(f(u,S)\) 表示\(S\) 中的結點構成以 \(u\) 為根的樹的方案數。轉移相當於劃分出一棵子樹,有:

\[f(u,S)=\sum_{v\in T\subseteq(S\setminus u)}f(v,T)f(u,S-T) \]

  不過這樣顯然會算重複。考慮任意固定子樹 \(T\) 內的某個點,設 \(p\not=u\)\(p\in T\),欽定 \(p\in T\) 就避免了重複,則:

\[f(u,S)=\sum_{v,p\in T\subseteq(S\setminus u)}f(v,T)f(u,S-T) \]

  注意 \(p\) 在求和過程中是常量


  接下來著手處理限制:

  • 限制 \(\text{LCA}\),設當前狀態 \(f(w,S)\),列舉到子集 \(T\)
    • \((u\in T)\land(v\in T)\Leftrightarrow\mathrm T\)(注意最後這個羅馬字型的 \(\mathrm T\)
      表示邏輯運算為真),\(u\)\(v\)\(\text{LCA}\) 必然在 \(T\) 中,所以必然不是 \(w\),矛盾。
    • \(q,r\)\(\text{LCA}\) 指定為 \(p\),且 \(p\in T\land(q\not\in T\lor q\not\in T)\Leftrightarrow \mathrm T\),即兩點的 \(\text{LCA}\) 深於其中至少一個點,顯然不滿足。
  • 限制鄰接點,同樣地設當前狀態 \(f(w,S)\),列舉到子集 \(T\)
    • \(u,v\not=r\) 鄰接,且 \((u\in T)\leftrightarrow(v\in T)\Leftrightarrow\mathrm F\)
      ,即有且僅有其中一點屬於 \(T\),矛盾。
    • \(u,v\) 均與 \(w\) 鄰接,且 \((u\in T)\land(v\in T)\Leftrightarrow\mathrm T\),即 \(w\) 向子樹 \(T\) 內的至少兩個點連邊,矛盾。

  轉移的時候判一下這四種情況就行啦。

  複雜度 \(\mathcal O(3^nn(n+m+q))\),不過跑不滿。為什麼我交一發當場最優解 rank1 呢 www?

\(\mathcal{Code}\)

#include <cstdio>
#include <vector>
#include <cstring>

#define bel( x, S ) ( ( S >> x ) & 1 )

typedef long long LL;
typedef std::pair<int, int> pii;

const int MAXN = 13;
int n, m, q;
LL f[MAXN + 5][1 << MAXN];
std::vector<int> adj[MAXN + 5];
std::vector<pii> dif[MAXN + 5]; 

inline bool check ( const int r, const int T ) {
	for ( pii p: dif[r] ) { // LCA情況1.
		if ( bel ( p.first, T ) && bel ( p.second, T ) ) {
			return false;
		}
	}
	for ( int u = 0; u < n; ++ u ) { // LCA情況2.
		if ( ! ( ( T >> u ) & 1 ) ) continue;
		for ( pii p: dif[u] ) {
			if ( ! bel ( p.first, T ) || ! bel ( p.second, T ) ) {
				return false;
			}
		}
	}
	for ( int u = 0; u < n; ++ u ) { // 鄰接情況1.
		if ( u == r ) continue;
		for ( int v: adj[u] ) {
			if ( v ^ r && bel ( u, T ) ^ bel ( v, T ) ) {
				return false;
			}
		}
	}
	int cnt = 0;
	for ( int u: adj[r] ) cnt += bel ( u, T ); // 鄰接情況2.
	return cnt <= 1;
}

inline LL solve ( const int r, int S ) {
	LL& ret = f[r][S];
	if ( ~ ret ) return ret;
	ret = 0, S ^= 1 << r; // 這裡注意去除根節點.
	int p;
	for ( p = 0; p < n && ! ( ( S >> p ) & 1 ); ++ p ); // 欽定一點p.
	for ( int T = S; T; T = ( T - 1 ) & S ) { // 列舉子集.
		if ( ! ( ( T >> p ) & 1 ) || ! check ( r, T ) ) continue;
		for ( int u = 0; u < n; ++ u ) {
			if ( ! bel ( u, T ) ) continue;
			ret += solve ( u, T ) * solve ( r, S ^ T ^ ( 1 << r ) );
		}
	}
	return ret;
}

int main () {
	scanf ( "%d %d %d", &n, &m, &q );
	for ( int i = 1, u, v; i <= m; ++ i ) {
		scanf ( "%d %d", &u, &v ), -- u, -- v;
		adj[u].push_back ( v ), adj[v].push_back ( u );
	}
	for ( int i = 1, u, v, w; i <= q; ++ i ) {
		scanf ( "%d %d %d", &u, &v, &w ), -- u, -- v, -- w;
		dif[w].push_back ( { u, v } );
	}
	memset ( f, 0xff, sizeof f );
	for ( int u = 0; u < n; ++ u ) f[u][1 << u] = 1; // 單點,一種方案.
	printf ( "%lld\n", solve ( 0, ( 1 << n ) - 1 ) );
	return 0;
}