【BZOJ】P2669 局部极小值

装压dp+容斥

由于当n=4,m=7时,局部极小值(下面简称极小值)最多只有8个。(自己模拟一下)
这样,我们就可以装压了。

状态定义

\(dp_{i,j}\)表示当前放到第i,极小值的状态为j时的方案数

状态转移

分两种情况:
1.在极小值中放i:
\(dp_{i,j<<(1<<(k-1))}+=dp_{i-1,j}(k \notin j)\)
2.在其他点钟放i
\(dp_{i,j}+=dp_{i,j-1} \times A\)
(A表示可以放的方案数)
对于A,我们可以预处理出每个j状态可行的方案数

此时的答案就是\(dp_{n \times m,(1<<tot)-1}\) (tot表示极小值的个数)

思考与质疑

想一想,要是在放的过程中某一个不是极小值的点变成极小值了咋办?
可以枚举那些点变成了极小值,每次计算dp值后在更新最终答案。
考虑到这些可能会有重合,(1个非极小值点变成了极小值必定包含了2个点变成了极小值的情况)
这就需要容斥了。

大致流程

1.开个dfs,枚举那些非极小值点可以变成极小值
2.当dfs到终点时,dp一下,并将最终答案更新
总之,就是dfs套dp

代码:


#include<bits/stdc++.h>
#define int long long
#define MOD 12345678
using namespace std;
int n,m,X[101],Y[101],tot,ans,Size[(1<<8)+10];
char mp[11][11];
bool can[11][11],mark[11][11];
const int mx[]= {1,0,-1,0,1,1,-1,-1,0},my[]= {0,1,0,-1,1,-1,1,-1,0};
int dp[30][(1<<8)+10],sum[(1<<8)+10];
int DP() {
    memset(dp,0,sizeof(dp));
    memset(sum,0,sizeof(sum));
    for(int i=0; i<(1<<tot); i++) {
        int res=0;
        memset(mark,false,sizeof(mark));
        for(int j=1; j<=tot; j++) {
            if((1<<(j-1))&i)continue;
            mark[X[j]][Y[j]]=true;
        }
        for(int j=1; j<=n; j++) {
            for(int k=1; k<=m; k++) {
                bool flag=true;
                for(int o=0; o<=8; o++) {
                    int nx=j+mx[o],ny=k+my[o];
                    if(mark[nx][ny]||can[j][k]) {
                        flag=false;
                        break;
                    }
                }
                res+=flag;
            }
        }
        sum[i]=res;
    }
    dp[0][0]=1;
    for(int i=1; i<=n*m; i++) {
        for(int j=0; j<(1<<tot); j++) {
            if(Size[j]>i)continue;
            dp[i][j]+=dp[i-1][j]*(max(sum[j]+Size[j]-i+1,0LL)),dp[i][j]%=MOD;
            for(int k=1; k<=tot; k++) {
                if((1<<(k-1))&j)continue;
                dp[i][j|(1<<(k-1))]+=dp[i-1][j],dp[i][j|(1<<(k-1))]%=MOD;
            }
        }
    }
    return dp[n*m][(1<<tot)-1];
}
bool check(int x,int y) {
    for(int i=0; i<=8; i++) {
        int nx=x+mx[i],ny=y+my[i];
        if(can[nx][ny])return false;
    }
    return true;
}
void DFS(int x,int y,int pos) {
    if(y==m+1)x++,y=1;
    if(x==n+1) {
        ans+=pos*DP(),ans+=MOD,ans%=MOD;
        return;
    }
    DFS(x,y+1,pos);
    if(check(x,y)) {
        can[x][y]=true,X[++tot]=x,Y[tot]=y;
        DFS(x,y+1,pos*-1);
        can[x][y]=false,tot--;
    }
}
signed main() {
    for(int i=1; i<(1<<8); i++)Size[i]=Size[i^(i&-i)]+1;
    scanf("%lld %lld",&n,&m);
    for(int i=1; i<=n; i++)scanf("%s",mp[i]+1);
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            if(mp[i][j]=='X') {
                can[i][j]=true;
                X[++tot]=i,Y[tot]=j;
            }
        }
    }
    DFS(1,1,1);
    cout<<ans;
    return 0;
}

posted @ 2019-07-15 21:15  TieT  阅读(185)  评论(0编辑  收藏  举报