题目
点这里看题目。
分析
首先我们可以考虑不存在任何限制的时候该怎么做。
根据 (Proverset{..}{u}fer) 序列直接得出答案为 (n^{n-2}) 。
忽略那种做法,因为它难以处理 LCA 的限制。看到 (n) 很小,我们可以想到一个状态压缩的 DP :
(f(u,S)):以 (u) 为根的子树中点集为 (S) 的方案数。
为了方便,以下我们也就用 ((u,S)) 表示一棵根为 (u) ,点集为 (S) 的子树。
之后,边界条件为 (f(u,{u})=1) ,答案就是 (f(1,V)) 。
然后存在转移:
直接转移会重复计数。例如这种情况:
((u,S)) 有两棵子树 ((v,S_1)) 和 ((w,S_2)) ,满足 (S=S_1cup S_2cup {u}) 。
那么 (f(u,Ssetminus S_1) imes f(v,S_1)) 和 (f(u,Ssetminus S_2) imes f(w,S_2)) 都会计算这种情况。
为了规避重复计算,我们需要钦定一个不为 (u) 特殊点,每次只枚举包含特殊点的 ((v,T)) 进行转移。
这就相当于始终在枚举特殊点所在的子树的情况,可以发现这样可以做到不重不漏。
那么考虑题目给出的限制,实际上就是在限制 DP 的转移。分开考虑:
先考虑 LCA 的限制等价于:
- 对于树 ((u, S)),如果存在 (k) ,使得 (c_k=u) ,那么 (a_i,b_i) 不能在同一个子树内,即不能进行 (a_iin Tland b_iin T) 的转移。
- 对于转移的子树 ((v, T)), 如果存在 (k) ,使得 (c_kin T) ,那么 (a_i,b_i) 也应该在 ((v,T)) 里面,即不能进行 (a_i otin Tlor b_i otin T) 的转移。
再考虑边的限制。一条边((u,v))同时包含了 (u) 是 (v) 的父亲 和 (v) 是 (u) 的父亲 两种情况,而前者在 (f(u,S)) 中计算,后者在 (f(v,S)) 中计算。故转移中我们只需要考虑 (u) 作为父亲的情况。
因而一条边等价于:
- 对于树 ((u,S)),如果不存在 (k) 使得 (u) 在限制边 (E_k=(x,y)) 上,那么转移的子树中,要么 (x otin Tland y ot in T) ,要么 (xin Tland yin T) 。
- 对于树 ((u,S)),如果存在 (k) ,使得 (u) 在限制边 (E_k=(u,w)) 上,那么转移的子树只能是 ((w, T)) ,而且 (T) 里面只能包含 (w) 一个与 (u) 有关的限制边端点。
在转移中途判断一下即可。同样的道理,写状压尽量记搜,因为状态的阶段通常是由 (|S|) 划分的。
时间是 (O(3^nk(n+m+q))) 。 CF 机子是出了名的快。
代码
#include <cstdio>
#include <cstring>
typedef long long LL;
const int MAXN = 15, MAXM = 105, MAXS = ( 1 << 13 ) + 5;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
LL f[MAXN][MAXS];
int LCARis[MAXM], LCAc[MAXM];
int Eu[MAXM], Ev[MAXM], ERis[MAXN];
int bit[MAXS], ref[MAXN];
int N, M, Q;
int mono( const int u ) { return 1 << u - 1; }
int lowbit( const int x ) { return x & ( -x ); }
int inter( const int S, const int T ) { return S & T; }
bool chk( const int S ) { return ! ( S - lowbit( S ) ); }
bool in( const int S, const int T ) { return ( S & T ) == T; }
bool onE( const int u, const int id ) { return Eu[id] == u || Ev[id] == u; }
LL DFS( const int u, const int S )
{
LL& ret = f[u][S];
if( ~ ret ) return ret; ret = 0;
if( ! in( S, ref[u] ) ) return ret = 0;
int pos; bool flag;
for( pos = 1 ; pos <= N ; pos ++ )
if( pos ^ u && in( S, ref[pos] ) ) break;
for( int T = ( S - 1 ) & S ; T ; T = ( T - 1 ) & S )
if( in( T, ref[pos] ) && ! in( T, ref[u] ) )
{
flag = true;
for( int i = 1 ; i <= Q ; i ++ )
if( LCAc[i] == u && in( T, LCARis[i] ) )
{ flag = false; break; }
//处理限制 1
if( ! flag ) continue;
for( int i = 1 ; i <= Q ; i ++ )
if( in( T, ref[LCAc[i]] ) && ! in( T, LCARis[i] ) )
{ flag = false; break; }
//处理限制 2
if( ! flag ) continue;
for( int i = 1 ; i <= M ; i ++ )
if( ! onE( u, i ) && ( in( T, ref[Eu[i]] ) ^ in( T, ref[Ev[i]] ) ) )
{ flag = false; break; }
//处理限制 3
if( ! flag ) continue;
if( ! inter( T, ERis[u] ) )
{
for( int i = 1 ; i <= N ; i ++ )
if( in( T, ref[i] ) )
ret += DFS( u, S ^ T ) * DFS( i, T );
}
//如果不包含任何限制边就枚举根进行转移
else if( chk( T & ERis[u] ) )
ret += DFS( u, S ^ T ) * DFS( bit[lowbit( T & ERis[u] )], T );
//否则需要钦定根转移
}
return ret;
}
int main()
{
read( N ), read( M ), read( Q );
memset( f, -1, sizeof f );
for( int i = 1 ; i <= N ; i ++ ) f[i][1 << i - 1] = 1, bit[ref[i] = ( 1 << i - 1 )] = i;
for( int i = 1 ; i <= M ; i ++ )
read( Eu[i] ), read( Ev[i] ),
ERis[Eu[i]] |= ref[Ev[i]], ERis[Ev[i]] |= ref[Eu[i]];
for( int i = 1, a, b ; i <= Q ; i ++ )
read( a ), read( b ), read( LCAc[i] ),
LCARis[i] = ref[a] | ref[b];
write( DFS( 1, ( 1 << N ) - 1 ) ), putchar( '
' );
return 0;
}