【Gym100837F】Controlled Tournament(状压Dp 搜索剪枝)

题目链接

大意

现有(N)个人要打比赛,知道任意两个人间打比赛的胜负关系。
要求在 深度最小 的情况下,根为(M)的 竞赛树 的个数。

满足(1le Mle Nle 16)

思路

虑及(N)如此之小的范围,不是状压就是暴搜。
考虑状态(Dp[s][u][d])表示在以点集(s)组成子树,(u)为根,深度不超过(d)的方案数。
那么转移就为(Dp[s][u][d]=Dp[s'][u][d]+Dp[s-s'][v][d]).其中(u)能击败(v)

然而,这样做的复杂度是(O(3^N imes N^2)),考虑变成记忆化搜索省掉无用状态。

考虑题目中给出的深度最小的条件,那么这棵树的深度一定是(left lceil log2(N) ight ceil),那么它的两棵子树深度一定都不会超过(left lceil log2(N)-1 ight ceil)
所以记搜时,对深度不符合的子树剪枝就行了,这样的话,搜索深度肯定就只能在(left lceil log2(N) ight ceil)以内,就可以过了。

代码

要用Freopen

#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=17;
int N,M,A[MAXN][MAXN];
int vis[MAXN],Q[MAXN],Len;
int Cnt[1<<MAXN],H[MAXN];
vector<int>P[MAXN];
long long Dp[1<<(MAXN-1)][MAXN][MAXN>>1];
int lowbit(int x){return x&(-x);}
long long DFS(int S,int rt,int dep){
	if(Dp[S][rt][dep]!=-1)return Dp[S][rt][dep];
	long long &ret=(Dp[S][rt][dep]=0);
	if(Cnt[S]==1){
		if(S&(1<<(rt-1)))return ret=1;
		return ret=0;
	}
	for(int x=S&(S-1);x;x=S&(x-1)){//其实对于枚举子集可以优化.
		int y=S-x;
		if(!(x&(1<<(rt-1))))continue;
		if(H[Cnt[x]]>dep-1)continue;
		if(H[Cnt[y]]>dep-1)continue;
		long long Ans1=0,Ans2=0;
		Ans1=DFS(x,rt,dep-1);
		int size=P[rt].size();
		for(int i=0;i<size;i++){
			int v=P[rt][i];
			if(!(y&(1<<(v-1))))continue;
			Ans2+=DFS(y,v,dep-1);
		}
		ret+=Ans1*Ans2;
	}
	return ret;
}
int main(){
	//freopen("f.in","r",stdin);
	//freopen("f.out","w",stdout);
	scanf("%d%d",&N,&M);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=N;j++){
			scanf("%d",&A[i][j]);
			if(A[i][j])P[i].push_back(j);
		}
	for(int i=1;i<=N;i++)H[i]=ceil(log2(i));
	for(int i=1;i<(1<<N);i++)
		Cnt[i]=Cnt[i>>1]+(i&1);
	memset(Dp,-1,sizeof(Dp));
	printf("%lld
",DFS((1<<N)-1,M,H[N]));
}
原文地址:https://www.cnblogs.com/ftotl/p/11839637.html