[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)\)

然后存在转移:

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

直接转移会重复计数。例如这种情况:

\((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 的限制等价于:

  1. 对于树 \((u, S)\),如果存在 \(k\) ,使得 \(c_k=u\) ,那么 \(a_i,b_i\) 不能在同一个子树内,即不能进行 \(a_i\in T\land b_i\in T\) 的转移。
  2. 对于转移的子树 \((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\) 作为父亲的情况。

因而一条边等价于:

  1. 对于树 \((u,S)\),如果不存在 \(k\) 使得 \(u\) 在限制边 \(E_k=(x,y)\) 上,那么转移的子树中,要么 \(x\not\in T\land y\not \in T\) ,要么 \(x\in T\land y\in T\)
  2. 对于树 \((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;
}
posted @ 2020-07-29 10:29  crashed  阅读(149)  评论(0编辑  收藏  举报