BZOJ 2669: [cqoi2012]局部极小值

能成为局部极小值的位置最多同时存在8个,可以用状态压缩dp

把1~n*m依次填入矩阵,f[i][j]表示放完第i个数,状态为j的方案数

f[i][j]=f[i-1][j]*(p[j]-i+1)+f[i-1][k] //预处理p数组,p[i]表示状态为i时可以填数的位置有哪些

信心满满地wa了几发,发现显然会把某些不是答案的方案给统计进来,于是用容斥来排除那些不符合题目的方案

最后又wa了几(5)发(数组开错了)

#include<cstdio>
#include<cstring>
#define wyxsb 12345678
using namespace std;
 
int n,m,ans=0;
int p[1<<10],f[30][1<<10],node[10][2],vis[10][10];
int a[10][10];
char ch[10];

int calc()
{
    memset(p,0,sizeof(p));
    int tp=0;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(a[i][j]) node[++tp][0]=i,node[tp][1]=j;
    for(int k=0;k<(1<<tp);++k)
    {
        memset(vis,0,sizeof(vis));
        for(int j=1;j<=tp;++j)
            if(!(k&(1<<j-1))) vis[node[j][0]][node[j][1]]=1;
        for(int i=1;i<=n;++i)
            for(int j=1;j<=m;++j)
            {
                bool flag=1;
                for(int x=-1;x<=1;++x)
                    for(int y=-1;y<=1;++y)
                        if(vis[i+x][j+y]) flag=0;
                if(flag) ++p[k];
            }
    }
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1;i<=n*m;++i)
        for(int j=0;j<(1<<tp);++j)
        {
            if(p[j]>i-1) f[i][j]=(f[i][j]+f[i-1][j]*(p[j]-i+1))%wyxsb;
            for(int k=1;k<=tp;++k)
                if(j&(1<<k-1)) f[i][j]=(f[i][j]+f[i-1][j^(1<<k-1)])%wyxsb;
        }
    return f[n*m][(1<<tp)-1];
}
 
void dfs(int x,int y,int f)
{
    if(x>n) {ans=(ans+((f&1)?-calc():calc()))%wyxsb;return;}		//容斥 
    if(y>m) {dfs(x+1,1,f);return;}
    dfs(x,y+1,f);
    bool flag=1;
    for(int i=-1;i<=1;++i)
        for(int j=-1;j<=1;++j)
            if(a[x+i][y+j]) return;
    a[x][y]=1;
    dfs(x,y+1,f+1);
    a[x][y]=0;
}
 
int main()
{
    scanf("%d %d",&n,&m);
    int i,j;
  for(i=1;i<=n;++i)
    {
        scanf("%s",ch);
        for(j=0;j<m;++j)
            a[i][j+1]=ch[j]=='X'?1:0;
    }
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(a[i][j])
                for(int x=-1;x<=1;++x)
                    for(int y=-1;y<=1;++y)
                        if((x||y)&&a[i+x][j+y])
                            return puts("0"),0;
    dfs(1,1,0);
    printf("%d",(ans+wyxsb)%wyxsb);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值