[Noip模拟题]山峰

Description
NM 的棋盘上不重复的填1到 NM ,如果一个数字比周围的八个数字大,那么他就是一个山峰。现在告诉你所有山峰的位置,问你填数的方案数mod 12345678

Input
输入第一行两个数字 N M意义如题目描述。
接下来 N 行,每行M个字符,’.’表示非山峰,’X’表示山峰。
1n4 m7

Output
仅一行,包含一个数字,为取模后的方案数。

Sample Input
1 3
.X.

Sample Output
2

HINT
并不是我写的题解

思路
这道题限制条件比较严格,山峰只能是给定的位置,其他的位置都不能是山峰。。。如果没有这个限制,那么直接状态压缩dp就可以了:从大到小枚举数字,设 fS,i 表示已经填了 i 个数字,填过的山峰为S的二进制状态,显然每个靠近山峰的数都可以填,其他的都不能填;也可以填一个没有填过的山峰。但是,有这个限制,那么就要用到容斥原理,多的减去,少的加上就可以了。

代码

#include <cstdio>
#include <cstring>

const int maxn=4;
const int maxm=7;
const int maxtot=8;
const int dx[]= {1,1,1,0,0,-1,-1,-1};
const int dy[]= {1,0,-1,1,-1,1,0,-1};
const int mo=12345678;

int n,m,ans;
char map[maxn+1][maxm+1];
int x[maxn*maxn+1],y[maxn*maxn+1],can[maxn+1][maxm+1];
int f[1<<maxtot][maxn*maxm+1];

int in_range(int x,int y)//这个点是否在图的范围内
{
    return (x<=n)&&(x>0)&&(y<=m)&&(y>0);
}

int x_around(int x,int y)//这个点周围是否有山峰
{
    for(int i=0; i<8; i++)
    {
        int nx=x+dx[i],ny=y+dy[i];
        if(in_range(nx,ny))
        {
            if(map[nx][ny]=='X')
            {
                return 1;
            }
        }
    }
    return 0;
}

int calc()//计算至少有给定的点是山峰时的方案数
{
    memset(f,0,sizeof f);
    int tot=0;
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            if(map[i][j]=='X')
            {
                tot++;
                x[tot]=i;
                y[tot]=j;
            }
        }
    }
    f[0][0]=1;
    for(int s=0; s<1<<tot; s++)
    {
        memset(can,1,sizeof can);
        for(int i=1; i<=tot; i++)
        {
            if(!(s&1<<(i-1)))
            {
                can[x[i]][y[i]]=0;
                for(int j=0; j<8; j++)
                {
                    int nx=x[i]+dx[j],ny=y[i]+dy[j];
                    if(in_range(nx,ny))
                    {
                        can[nx][ny]=0;
                    }
                }
            }
        }
        int cnt=0;
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)
            {
                if(can[i][j])
                {
                    cnt++;
                }
            }
        }
        for(int i=0; i<=cnt; i++)
        {
            if(f[s][i])
            {
                f[s][i+1]=(f[s][i+1]+f[s][i]*(cnt-i))%mo;
                for(int j=1; j<=tot; j++)
                {
                    if(!(s&1<<(j-1)))
                    {
                        f[s|1<<(j-1)][i+1]=(f[s|1<<(j-1)][i+1]+f[s][i])%mo;
                    }
                }
            }
        }
    }
    return f[(1<<tot)-1][n*m];
}

int search(int x,int y,int k)
//寻找到了(x,y)这个点,找到答案后如果k=1那么就将答案加上这个值,否则减去这个值
{
    if(x>n)
    {
        ans=(ans+k*calc())%mo;
        return 0;
    }
    if(y>m)
    {
        search(x+1,1,k);
        return 0;
    }
    search(x,y+1,k);
    if((!x_around(x,y))&&(map[x][y]=='.'))
    {
        map[x][y]='X';
        search(x,y+1,-k);
        map[x][y]='.';
    }
    return 0;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
    {
        scanf("%s",map[i]+1);
    }
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            if((map[i][j]=='X')&&x_around(i,j))
            {
                putchar('0');
                putchar('\n');
                return 0;
            }
        }
    }
    search(1,1,1);
    printf("%d\n",(ans+mo)%mo);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值