BZOJ2669: [cqoi2012]局部极小值-状压DP+容斥

传送门

题意:

有一个n行m列的整数矩阵,其中1到 nm n ∗ m 之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。给出所有局部极小值的位置,判断有多少个可能的矩阵。

(1n4,1m7) ( 1 ≤ n ≤ 4 , 1 ≤ m ≤ 7 )

Solution:

显然我们把矩阵所有的状态存下来是不行的,所以说我们考虑减少我们需要存储的状态:我们的局部极小值最多只会有8个

那么我们也可以对这8个极小值进行状压

考虑将值从小到大填: f[i][S] f [ i ] [ S ] 表示有S状态的局部极小值已经被填了,已经填了1~i的方案数

这样我们每次转移时考虑这个值填在局部极小值的位置还是其他位置:

cnt[S] c n t [ S ] 表示当局部极小值填的状态是S时,不填到局部极小值有多少种填法,因为还没填的局部极小值的周围都不能先填,所以这个可以简单地计算出来

转移即为 f[i][S]=f[i1][S](cnt[S]i+1)+pS,|p|=1f[i1][Sp] f [ i ] [ S ] = f [ i − 1 ] [ S ] ∗ ( c n t [ S ] − i + 1 ) + ∑ p ⊆ S , | p | = 1 f [ i − 1 ] [ S − p ]

但是这样求可能会求出有更多局部极小值的方案,所以我们需要容斥

我们用总方案数-多出一个极小值的方案数+多出两个极小值的方案数…

总复杂度上限 O(2828nm) O ( 2 8 ∗ 2 8 ∗ n ∗ m )

代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int mod=12345678;
int f[30][1<<8];
int n,m,qx[10],qy[10],cnt[1<<8],ans;
char s[10];
bool tag[10][10],vis[10][10];
int dx[8]={0,0,1,-1,1,1,-1,-1};
int dy[8]={1,-1,1,-1,0,-1,0,1};
void dp(int v)
{
    int tot=0;
    for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) if (tag[i][j]) qx[tot]=i,qy[tot]=j,tot++;
    for (int S=0;S<(1<<tot);S++)
    {
        cnt[S]=0;for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) vis[i][j]=0;
        for (int i=0;i<tot;i++)
            if (!((S>>i)&1))
            {
                for (int k=0;k<8;k++)
                {
                    int nx=qx[i]+dx[k],ny=qy[i]+dy[k];
                    if (nx<1||nx>n||ny<1||ny>m)  continue;
                    vis[nx][ny]=1;
                }
                vis[qx[i]][qy[i]]=1;
            }
        for (int i=1;i<=n;i++)
            for (int j=1;j<=m;j++) if (!vis[i][j]) cnt[S]++; 
    //  cout<<S<<" "<<cnt[S]<<endl;
    }
    f[0][0]=1;
    for (int i=1;i<=n*m;i++)
        for (int S=0;S<(1<<tot);S++)
        {
            f[i][S]=1ll*f[i-1][S]*(cnt[S]-i+1)%mod;
            for (int j=0;j<tot;j++)
                if ((S>>j)&1) f[i][S]=(f[i-1][S-(1<<j)]+f[i][S])%mod;
        }
    ans=(ans+v*f[n*m][(1<<tot)-1]+mod)%mod;
}
void dfs(int x,int y,int ifi)
{
    if (y==m+1) {dfs(x+1,1,ifi);return;}
    if (x==n+1) {dp(ifi);return;}
    dfs(x,y+1,ifi);
    bool can=1;
    for (int k=0;k<8;k++)
    {
        int nx=x+dx[k],ny=y+dy[k];
        if (nx<1||nx>n||ny<1||ny>m)  continue;
        if (tag[nx][ny]) can=0;
    }
    if (tag[x][y]) can=0;
    if (can) tag[x][y]=1,dfs(x,y+1,-ifi),tag[x][y]=0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s+1);for (int j=1;j<=m;j++) if (s[j]=='X') tag[i][j]=1;
    }
    dfs(1,1,1);
    printf("%d",ans);
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值