[省选前题目整理][BZOJ 1087][SCOI2005]互不侵犯King

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1087

思路

首先预处理出对于单独的一行而言的所有合法的状态,然后预处理出相邻两行合法的状态对 (S1,S2) 。然后直接DP就行了

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 100

using namespace std;

typedef long long int LL;

LL f[MAXN][MAXN][600],ans; //f[i][j][S]=DP到第i行,第i行状态为S,已经用了j个棋子的方案数
int status[MAXN];
int map[MAXN][MAXN],cnt[MAXN]; //map[i][j]=true表示上一行状态为i,这一行状态为j是可行的,cnt[i]=第i种状态对应的棋子数
int n,k,tot=0; //tot=单独一行的可行状态总数

void predfs(int x,int pos,int now) //已经放了x个棋子,上一次在pos位置放了一个棋子
{
    status[++tot]=now;
    cnt[tot]=x;
    if(x>=(n+1)/2||x>=k) return;
    for(int i=pos+2;i<=n;i++)
        predfs(x+1,i,now+(1<<(i-1)));
}

void prework() //预处理出map数组
{
    for(int i=1;i<=tot;i++)
        for(int j=1;j<=tot;j++)
        {
            if((status[i]&status[j])||((status[i]>>1)&status[j])||((status[i]<<1)&status[j]))
                map[i][j]=0;
            else
                map[i][j]=1; //!!!!
        }
    for(int i=1;i<=tot;i++) //dp预处理
        f[1][cnt[i]][i]=1; //!!!!
}

int main()
{
    scanf("%d%d",&n,&k);
    predfs(0,-1,0);
    prework();
    for(int i=2;i<=n;i++) //第i行
        for(int j=0;j<=k;j++) //已经用了j个棋子
            for(int nextS=1;nextS<=tot;nextS++) //第i行状态为nextS
            {
                if(cnt[nextS]>j) continue;
                for(int S=1;S<=tot;S++) //枚举第i-1行状态nextS
                    if(map[S][nextS]&&cnt[S]+cnt[nextS]<=j)
                        f[i][j][nextS]+=f[i-1][j-cnt[nextS]][S];
            }
    LL ans=0;
    for(int i=1;i<=tot;i++)
        ans+=f[n][k][i];
    printf("%lld\n",ans);
    return 0;
}

你可能感兴趣的:([省选前题目整理][BZOJ 1087][SCOI2005]互不侵犯King)