zoukankan      html  css  js  c++  java
  • BZOJ 2669 CQOI2012 局部极小值 状压dp+容斥原理

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2669

    题意概述:实际上原题意很简洁了我就不写了吧。。。。

    二话不说先观察一下性质,首先棋盘很窄,可以乱搞的样子,然后注意到如果一个点是局部极小值那么周围3*3矩阵内不能有另一个局部最小值。于是画个图发现题目的数据范围最多有8个局部最小值。性质大概就是这些了。

    暴力实际上是搜索,本质是多阶段决策问题。由于棋盘很小,容易让人联想到搞个插头dp之类东西来弄一下,依次填每个格子来作为一个决策阶段。然后就发现。。。这个东西太复杂了。。。等你想出来不知道什么时候去了(而且很有可能想不出来)。。。于是需要换个思路,把决策阶段改一下,每一行作为阶段的话更加复杂。。。弃疗。那么决策阶段很有可能就不是按照格子的顺序来的了。注意到局部最小值一定是周围的格子里面最小的那个,并且最多只有8个局部最小值,那么尝试从小到大填充每一个数字作为阶段,把局部最小值是否填充的状态压进去。

    于是令f(s,i)表示用1~i的数字填充棋盘,集合s中的局部最小值已经被填充的方案数。

    分析这种决策下的性质,发现一个局部最小值一定是以其为中心3*3内最先填的那个,也就是说如果一个局部最小值没有填充,那么周围3*3的点都不能填充。排除所有不可以填充的点剩下的就是可以填充的点,令cnt[s]表示局部极小值填充状态为s时的棋盘上最多有几个数字。

    得到f(s,i)=f(s,i-1)*C(cnt[s]-i+1,1)+sum{ f(s-{j},i-1) | j是s的子集 },时间复杂度O(N*M*X*2^X),X表示棋盘上有多少个局部极小值。

    但是可以注意到在状态设计的时候可以保证题目给出的X都成为局部最小值,但是可能让不是局部最小值的位置变成局部最小值。

    于是这题最神的地方来了:容斥!由于棋盘很小,所以说打个回溯跑一下局部极小值的分布位置发现最多也就16000多的方案。我们用回溯跑出每一种题目要求位置为局部极小值的棋盘状态,对于每个状态来一次dp,然后根据有多少个额外的点是局部极小值进行奇偶容斥就得出答案。

    时间复杂度O(O(容斥)*N*M*X*2^X)

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cstdlib>
     5 #include<algorithm>
     6 #include<cmath>
     7 #include<queue>
     8 #include<set>
     9 #include<map>
    10 #include<vector>
    11 #include<cctype>
    12 using namespace std;
    13 const int maxn=6;
    14 const int maxm=9;
    15 const int maxs=(1<<8)+2;
    16 const int mo=12345678;
    17 
    18 int N,M,X,ans;
    19 char mp[maxn][maxm];
    20 struct XY{ int x,y; }p[10];
    21 int bin[10],f[maxs][4*7+5],cnt[maxs],vis[maxn][maxm],sz[maxs];
    22 
    23 void data_in()
    24 {
    25     scanf("%d%d",&N,&M);
    26     for(int i=1;i<=N;i++){
    27         scanf("%s",mp[i]+1);
    28         for(int j=1;j<=M;j++)
    29             if(mp[i][j]=='X') p[++X]=(XY){i,j};
    30     }
    31     for(int i=1;i<=9;i++) bin[i]=1<<i-1;
    32     for(int s=1;s<bin[9];s++) sz[s]=sz[s>>1]+(s&1);
    33 }
    34 void dp(int tot)
    35 {
    36     for(int s=0;s<bin[tot+1];s++){
    37         cnt[s]=0;
    38         memset(vis,0,sizeof(vis));
    39         for(int j=1;j<=tot;j++) if(!(bin[j]&s)){
    40             int x=p[j].x,y=p[j].y;
    41             vis[x-1][y-1]=vis[x-1][y]=vis[x-1][y+1]=
    42             vis[x][y-1]=vis[x][y]=vis[x][y+1]=
    43             vis[x+1][y-1]=vis[x+1][y]=vis[x+1][y+1]=1;
    44         }
    45         for(int i=1;i<=N;i++)
    46         for(int j=1;j<=M;j++) cnt[s]+=1-vis[i][j];
    47     }
    48     memset(f,0,sizeof(f));
    49     f[0][0]=1;
    50     for(int i=1;i<=cnt[0];i++)
    51         f[0][i]=f[0][i-1]*(cnt[0]-i+1)%mo;
    52     for(int s=1;s<bin[tot+1];s++)
    53     for(int i=sz[s];i<=cnt[s];i++){
    54         f[s][i]=f[s][i-1]*(cnt[s]-i+1)%mo;
    55         for(int j=1;j<=tot;j++) if(bin[j]&s)
    56             f[s][i]=(f[s][i]+f[s^bin[j]][i-1])%mo;
    57     }
    58 }
    59 bool check(int x,int y) { return mp[x-1][y-1]!='X'&&mp[x-1][y]!='X'&&mp[x-1][y+1]!='X'&&mp[x][y-1]!='X'; }
    60 void dfs(int x,int y,int n)
    61 {
    62     if(x>N){
    63         dp(X+n);
    64         if(n%2==0) ans=(ans+f[bin[X+n+1]-1][N*M])%mo;
    65         else ans=(ans-f[bin[X+n+1]-1][N*M]+mo)%mo;
    66         return;
    67     }
    68     int xx=x,yy=y+1;
    69     if(yy>M) xx++,yy=1;
    70     if(mp[x][y]=='X'){
    71         if(check(x,y)) dfs(xx,yy,n);
    72     }
    73     else{
    74         if(check(x,y)){
    75             mp[x][y]='X',p[X+n+1]=(XY){x,y};
    76             dfs(xx,yy,n+1);
    77             mp[x][y]='.';
    78         }
    79         dfs(xx,yy,n);
    80     }
    81 }
    82 void work()
    83 {
    84     dfs(1,1,0);
    85     printf("%d
    ",ans);
    86 }
    87 int main()
    88 {
    89     data_in();
    90     work();
    91     return 0;
    92 }
  • 相关阅读:
    《大道至简》第一章 编程的精义
    java课堂练习7
    Java课后练习6
    Java课后练习5
    Java课后练习4
    Java课后练习3
    课堂练习
    求和程序实验报告
    大道至简第二章读后感
    课堂作业例子
  • 原文地址:https://www.cnblogs.com/KKKorange/p/8478183.html
Copyright © 2011-2022 走看看