[CF599E] Sandy and Nuts

题目

点这里看题目。

分析

首先我们可以考虑不存在任何限制的时候该怎么做。

根据 (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))

然后存在转移:

[f(u,S)=sum_{vin Tsubseteq S} f(u,Ssetminus T) imes f(v,T) ]

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

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

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

因而一条边等价于:

  1. 对于树 ((u,S)),如果不存在 (k) 使得 (u) 在限制边 (E_k=(x,y)) 上,那么转移的子树中,要么 (x otin Tland y ot in T) ,要么 (xin Tland yin 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( '
' );
	return 0;
}
原文地址:https://www.cnblogs.com/crashed/p/13395632.html