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

题目链接

大意

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

满足\(1\le M\le N\le 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\times N^2)\),考虑变成记忆化搜索省掉无用状态。

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

代码

要用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\n",DFS((1<<N)-1,M,H[N]));
}
posted @ 2019-11-12 08:55  孤攀客  阅读(172)  评论(0编辑  收藏  举报