BZOJ2669 局部极小值

题目

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

给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。

思路

看到数据范围马上想到状压dp。

先分析状态数,会发现局部极小值在一张图中最多只有8个,所以状态为已经实现了哪些局部极小值。\(dp[i][S]\)为已经填完数\([1,i]\),满足局部极小值为\(S​\)的方案数。

则考虑从已知的位置向后转移,下一个填数的位置可以是某个未填的局部极小值,也可以是除了未填过的局部最小值旁边的点之外的其它点,我们可以预处理这些点的数目\(num[i]\)

两个转移方程如下:
\[ dp[i+1][S+(1<<j)]\leftarrow{dp[i][S]}\\ dp[i+1][s]\leftarrow{(num[s]−(i−∣s∣))×dp[i][s]} \]
然后要注意的是状态的定义:我们当前的\(dp\)值仅仅满足了需要成为局部极小值的点成为了局部极小值,而不能保证是否除了这些点之外还有其它的局部极小值,所以这里做一个容斥就好了。

代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int mod=12345678;
void Add(int &x,int y){
    x+=y;
    if(x<0)x+=mod;
    if(x>=mod)x-=mod;   
}
int n,m;
char S[15][15];
struct node{int x,y;}P[10];
bool check(int x,int y){return x>=1&&x<=n&&y>=1&&y<=m;}
int dp[30][1<<8|5],tt,cnt[1<<8|5];
bool vis[15][15],mark[5][8];
int calc(){
    for(int i=1;i<1<<tt;i++){
        memset(mark,0,sizeof(mark));cnt[i]=0;
        for(int j=0;j<tt;j++)
            if(i&1<<j){
                for(int x=P[j].x-1;x<=P[j].x+1;x++)
                    for(int y=P[j].y-1;y<=P[j].y+1;y++){
                        if(!check(x,y)||mark[x][y])continue;
                        mark[x][y]=1;cnt[i]++;
                    }
            }
    }
    dp[0][0]=1;
    for(int i=1;i<=n*m;i++){
        for(int j=0;j<1<<tt;j++){
            dp[i][j]=0;
            Add(dp[i][j],1LL*dp[i-1][j]*(n*m-cnt[((1<<tt)-1)^j]-i+1)%mod);
            for(int q=0;q<tt;q++)
                if(j&1<<q)
                    Add(dp[i][j],dp[i-1][j^(1<<q)]);
        }
    }
    return dp[n*m][(1<<tt)-1];
}
int dfs(int x,int y,int f){
    if(x>n)return calc()*f;
    if(y>m)return dfs(x+1,1,f);
    int res=0;
    if(S[x][y]=='X'){
        P[tt++]=(node){x,y};
        Add(res,dfs(x,y+1,f));tt--;
        return res;
    }
    bool flag=1;
    for(int i=x-1;i<=x+1;i++)
        for(int j=y-1;j<=y+1;j++)
            if(S[i][j]=='X'||vis[i][j])flag=0;
    if(flag){
        P[tt++]=(node){x,y};vis[x][y]=1;
        Add(res,dfs(x,y+1,-f));tt--;vis[x][y]=0;
    }
    Add(res,dfs(x,y+1,f));
    return res;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%s",S[i]+1);
    printf("%d\n",dfs(1,1,1));
    return 0;
}

转载于:https://www.cnblogs.com/zryabc/p/11202115.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值