【CQOI 2012】【JZOJ 4700】【BZOJ 2669】局部极小值

Description

这里写图片描述

抽象题意:在一个矩阵中依次填入数,要求只有规定的数是小于它周边的数。

Solution

先来考虑一下当每一个.都有与它相邻的X,
因为棋盘很小,最多有8个X,
用状态压缩:
fi,S 表示当前选到数字i,状态为S,

fi,S=fia,S(RSi+1)+kSfi1,K

RS 表示在i的状态下,总共有多少个位置可以填,
我们发现,这样会算重,所有要用容斥原理来做,
复杂度: O(2m12knm) ,k表示有多少个X,m1表示可以填的位置数;

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define OK(q,w) ((q)>0&&(w)>0&&(q)<=n&&(w)<=m)
using namespace std;
typedef long long LL;
const int N=10,mo=12345678;
int m,n,ans,m1;
int a[N][N];
LL R[1<<10]; 
LL f[N*N][1<<10];
int er[21];
bool z[N][N],z1[N][N];
int fx[8][2]={{-1,0},{-1,-1},{0,-1},{-1,1},{1,-1},{1,1},{1,0},{0,1}};
bool OKX(int i,int j,int e)
{
    fo(k,0,e)if(OK(i+fx[k][0],j+fx[k][1]))if(a[i+fx[k][0]][j+fx[k][1]])return 1;
    return 0;
}
int doit()
{
    fo(I,0,er[m1+1]-1)
    {
        R[I]=0;
        fo(i,1,n)fo(j,1,m)
        {
            bool z=1;
            fo(k,0,7)if(OK(i+fx[k][0],j+fx[k][1]))
            if(a[i+fx[k][0]][j+fx[k][1]]&&!(er[a[i+fx[k][0]][j+fx[k][1]]]&I))z=0;
            if(((er[a[i][j]]&I)||!a[i][j])&&z)R[I]++;
        }
    }
    f[0][0]=1;
    fo(i,1,n*m)
        fo(I,0,er[m1+1]-1)
        {
            LL t=0;
            fo(j,1,m1)if(er[j]&I)t=(t+f[i-1][I-er[j]])%mo;
            f[i][I]=(f[i-1][I]*(R[I]-i+1)%mo+t)%mo;
        }
    return f[n*m][er[m1+1]-1];
}
void ss(int q,int w,int e)
{

    if(w>m)q++,w=1;
    if(q>n){ans=(ans+doit()*((e%2)?-1:1))%mo;return;}
    ss(q,w+1,e);
    if(!a[q][w]&&!z[q][w]&&z1[q][w])
    {
        a[q][w]=++m1;
        z1[q+1][w-1]=z1[q+1][w]=z1[q+1][w+1]=0;
        ss(q,w+2,e+1);
        a[q][w]=0;m1--;
        z1[q+1][w-1]=z1[q+1][w]=z1[q+1][w+1]=1;
    }
}
int main()
{
    int q,w,_;
    er[1]=1;fo(i,2,20)er[i]=er[i-1]<<1;
    scanf("%d",&_);
    while(_--)
    {
        scanf("%d%d",&n,&m);
        m1=0;
        fo(i,1,n)
            fo(j,1,m)
            {
                char ch=' ';a[i][j]=0;
                while(ch!='.'&&ch!='X')ch=getchar();
                if(ch=='X')a[i][j]=++m1;
            }
        fo(i,1,n)fo(j,1,m)
        {
            z1[i][j]=!(z[i][j]=OKX(i,j,7));
            if(a[i][j]&&z[i][j])m1=0;
        }
        if(!m1){printf("0\n");continue;}
        ans=0;
        ss(1,1,0);
        printf("%lld\n",(ans+mo)%mo);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值