[CF599E] Sandy and Nuts
题目
点这里看题目。
分析
首先我们可以考虑不存在任何限制的时候该怎么做。
根据 \(Pr\overset{..}{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_1\cup S_2\cup \{u\}\) 。
那么 \(f(u,S\setminus S_1)\times f(v,S_1)\) 和 \(f(u,S\setminus S_2)\times f(w,S_2)\) 都会计算这种情况。
为了规避重复计算,我们需要钦定一个不为 \(u\) 特殊点,每次只枚举包含特殊点的 \((v,T)\) 进行转移。
这就相当于始终在枚举特殊点所在的子树的情况,可以发现这样可以做到不重不漏。
那么考虑题目给出的限制,实际上就是在限制 DP 的转移。分开考虑:
先考虑 LCA 的限制等价于:
- 对于树 \((u, S)\),如果存在 \(k\) ,使得 \(c_k=u\) ,那么 \(a_i,b_i\) 不能在同一个子树内,即不能进行 \(a_i\in T\land b_i\in T\) 的转移。
- 对于转移的子树 \((v, T)\), 如果存在 \(k\) ,使得 \(c_k\in T\) ,那么 \(a_i,b_i\) 也应该在 \((v,T)\) 里面,即不能进行 \(a_i\not\in T\lor b_i\not\in T\) 的转移。
再考虑边的限制。一条边\((u,v)\)同时包含了 \(u\) 是 \(v\) 的父亲 和 \(v\) 是 \(u\) 的父亲 两种情况,而前者在 \(f(u,S)\) 中计算,后者在 \(f(v,S)\) 中计算。故转移中我们只需要考虑 \(u\) 作为父亲的情况。
因而一条边等价于:
- 对于树 \((u,S)\),如果不存在 \(k\) 使得 \(u\) 在限制边 \(E_k=(x,y)\) 上,那么转移的子树中,要么 \(x\not\in T\land y\not \in T\) ,要么 \(x\in T\land y\in 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( '\n' );
return 0;
}