【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]));
}

猜你喜欢

转载自www.cnblogs.com/ftotl/p/11839637.html