BZOJ 2669 cqoi2012 局部极小值 状压DP+容斥原理

13 篇文章 1 订阅
12 篇文章 1 订阅

题目大意:给定一个 nm 的矩阵,标记出其中的局部极小值,要求填入 1...nm ,求方案数

《多年的心头大恨终于切掉了系列》
考虑将数字从小到大一个一个填进去
由于局部极小值最多 8 个,我们可以状压DP
fi,j表示已经填完了前 i 个数,局部极小值的填充状态为j的方案数
预处理出 cntj 表示填充状态为 j 时共有多少位置是可以填充的(包括已填充的局部极小值位置)
那么有DP方程fi,j=fi1,jC1cntji+1+kjfi1,j{k}
但是问题是这样虽然保证了标记的位置都是局部最小值,但是可能会导致一些未标记的位置成为局部极小值,因此我们枚举其他可以成为局部极小值的位置,容斥一下即可

时间复杂度 O(DFS8nm28)
其中 DFS 最大为 16334 ,判掉全空的状态后最大为 4749 ,足以通过所有数据

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MOD 12345678
using namespace std;
const int dx[]={-1,-1,-1,0,0,1,1,1,0};
const int dy[]={-1,0,1,-1,1,-1,0,1,0};
int n,m,ans;
char s[10][10];
int Calculate()
{
    static pair<int,int> stack[10];
    static int cnt[1<<8],f[30][1<<8];
    int i,j,k,sta,top=0;
    memset(cnt,0,sizeof cnt);
    memset(f,0,sizeof f);
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            if(s[i][j]=='X')
                stack[++top]=pair<int,int>(i,j);
    for(sta=0;sta<1<<top;sta++)
    {
        static bool unfilled[10][10];
        memset(unfilled,0,sizeof unfilled);
        for(i=1;i<=top;i++)
            if(~sta&(1<<i-1))
                unfilled[stack[i].first][stack[i].second]=true;
        for(i=1;i<=n;i++)
            for(j=1;j<=m;j++)
            {
                for(k=0;k<9;k++)
                    if(unfilled[i+dx[k]][j+dy[k]])
                        break;
                if(k==9)
                    cnt[sta]++;
            }
    }
    f[0][0]=1;
        for(i=1;i<=n*m;i++)
            for(sta=0;sta<1<<top;sta++)
            {
                (f[i][sta]+=(long long)f[i-1][sta]*max(cnt[sta]-i+1,0))%=MOD;
                for(j=1;j<=top;j++)
                    if(sta&(1<<j-1))
                        (f[i][sta]+=f[i-1][sta^(1<<j-1)])%=MOD;
            }
    return f[n*m][(1<<top)-1];
}
void DFS(int x,int y,int cnt)
{
    int i;
    if(y==m+1)
    {
        DFS(x+1,1,cnt);
        return ;
    }
    if(x==n+1)
    {
        (ans+=Calculate()*(cnt&1?-1:1))%=MOD;
        return ;
    }
    DFS(x,y+1,cnt);
    for(i=0;i<9;i++)
        if(s[x+dx[i]][y+dy[i]]=='X')
            break;
    if(i==9)
    {
        s[x][y]='X';
        DFS(x,y+1,cnt+1);
        s[x][y]='.';
    }
}
int main()
{
    int i,j,k;
    cin>>n>>m;
    for(i=1;i<=n;i++)
        scanf("%s",s[i]+1);
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            if(s[i][j]=='X')
                for(k=0;k<8;k++)
                    if(s[i+dx[k]][j+dy[k]]=='X')
                        return puts("0"),0;
    DFS(1,1,0);
    cout<<(ans+MOD)%MOD<<endl;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值