bzoj 2669 局部极小值

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

有一个和dp求n的排列中有k个逆序对的方案数一样的转化
从小到大放数,那么如果一个是局部极小值的格子没有被放数,那么周围都不能放数。由于最多有8个位置是局部极小值,所以可以状压哪些格子放了。
令cnt[S]表示当前状态已经被放了局部极小值的格子的总数和不是局部极小值的格子中可以放的格子的总和。f[S][i]表示状态为S,放了前i个数的方案数。那么f[S][i]=sigma(f[S^j][i-1](j&S>0))+[cnt[S]-(i-1)]*f[S][i-1]
然后会有一些不是局部极小值的位置出现了局部最小值,容斥掉就行了

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>

#define ll long long
#define inf 1e9
#define eps 1e-10
#define md 12345678
#define N 1010
using namespace std;
bool mp[6][9],ok[6][9];
int cnt[N];
ll f[1010][100];
int n,m,mxn; ll ans=0;
char st[110];
struct point { int x,y;} q[100];
int lx[8]={1,0,-1,0,1,1,-1,-1},ly[8]={0,1,0,-1,1,-1,1,-1};
ll solve()
{
int tot=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (mp[i][j]) q[tot++]=(point){i,j};
for (int S=0;S<(1<<tot);S++)
{
cnt[S]=0;
memset(ok,0,sizeof(ok));
for (int e=0;e<tot;e++)
{
if (!((S>>e)&1))
{
int i=q[e].x,j=q[e].y;
for (int k=0;k<=7;k++)
{
int x=i+lx[k],y=j+ly[k];
if (x<1||y<1||x>n||y>m) continue;
ok[x][y]=1;
}
} else cnt[S]++;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if ((!ok[i][j]&&(!mp[i][j]))) cnt[S]++;
}
//printf("cnt: "); for (int i=0;i<(1<<tot);i++) printf("%d ",cnt[i]); printf("\n");
memset(f,0,sizeof(f));
f[0][0]=1;
for (int i=0;i<=mxn;i++)
{
for (int S=0;S<(1<<tot);S++)
{
if (i&&(cnt[S]-(i-1))>0) f[S][i]=(f[S][i]+f[S][i-1]*(cnt[S]-(i-1))%md)%md;
if (f[S][i]==0) continue;
for (int j=0;j<tot;j++)
{
if (!((S>>j)&1)) f[S|(1<<j)][i+1]=(f[S|(1<<j)][i+1]+f[S][i])%md;
}
}
}/*
for (int i=0;i<=mxn;i++)
{
for (int S=0;S<(1<<tot);S++) printf("%lld ",f[S][i]);
printf("\n");
}printf("\n");*/
return f[(1<<tot)-1][mxn];
}
void outit()
{
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++) printf("%d ",mp[i][j]);
printf("\n");
}
}
void dfs(int dep,int s)
{
//printf("dep: %d %d\n",dep,s); outit();
if (dep>mxn)
{
ll x=solve(); //printf("%lld\n",x);
if (s&1) ans=(ans-x+md)%md; else ans=(ans+x)%md;
return;
}
dfs(mxn+1,s);
for (int now=dep;now<=mxn;now++)
{
int i=(now-1)/m+1,j=(now-1)%m+1;
//printf("%d %d %d\n",now,i,j);
if (!mp[i][j])
{
bool ok=1;
for (int k=0;k<=7;k++)
{
int x=i+lx[k],y=j+ly[k];
if (x<1||y<1||x>n||y>m) continue; //printf("xy: %d %d\n",x,y);
if (mp[x][y]) { ok=0; break;}
}
if (ok)
{
mp[i][j]=1;
dfs(now+1,s+1);
mp[i][j]=0;
}
}
}
}

void check()
{
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
if (mp[i][j])
{
for (int k=0;k<=7;k++)
{
int x=i+lx[k],y=j+ly[k];
if (mp[x][y])
{
printf("0\n");
exit(0);
}
}
}
}
}

int main()
{
scanf("%d%d",&n,&m);
mxn=n*m;
for (int i=1;i<=n;i++)
{
scanf("%s",st+1);
for (int j=1;j<=m;j++) mp[i][j]=st[j]=='X';
}
check();
dfs(1,0);
printf("%d\n",ans);
return 0;
}


你可能感兴趣的:(bzoj 2669 局部极小值)