【连通性状态压缩DP】URAL1519

URAL1519(vjudge)
题意:给出一个n*m的矩阵,有的是空白格,有的是障碍格。你的任务是用一条回路来遍历整个矩阵的空白格,不能经过障碍格。统计这样的回路数量。

【连通性状态压缩DP】URAL1519_第1张图片

题解:
这道题和hdu1693唯一的不同,就是它限定了只有一条回路(但却恶心了好多倍)。一条回路,即意味着每个插头属于同一个联通块。所以在hdu1693的基础上,我们还要确定每个插头的连通性。这里仅介绍编码复杂度小,耗时较多的方法:最小整数表示法。

在hdu1693中,我们将一条轮廓线用二进制来表示,那么在这里就不能单单地用0/1来表示了,因为要区分不同的联通情况,所以在每个插头用一个特定的值表示它所属的联通块(注意!这里并不是联通块的编号,因为在单一的状态下,我们只需要区分在状态中各个插头的联通关系,不需要考虑全局)。

不难得到联通块的个数最多为m/2(详见注释),所以在m<=12时,联通块最多为6个,但这并不代表我们就得用7进制来存储状态,这里有一个经验性的结论:状态用2^x来存储时时间往往会快很多。所以我们选择用8进制来存储,总的内存即为(m+1)^8,已经爆内存了。但很容易发现有许多状态都是不会存在的,所以为了节约空间,我们用hash来存储。

转移就是本题的精华:
如果转移格有左插头与上插头,即如果要更新下一次,必定会使它左边和上面的联通块连起来,如果它们属于不同的联通块,那么把它们连起来不会有问题,但如果它们恰好属于一个联通块,那么就意味着当你将他们连起来时,就形成了一个回路。题目要求的回路只有一个,所以想象一下就会发现这个回路的右下角的格子一定是只有左上插头,所以只有在最靠后的一个格子中允许两个属于相同联通块的格子连起来,否则就会形成多条回路(这里如果觉得不好理解可以画画图)

其他的转移就很容易了
如果转移格只有一个插头,只需要判定一下右边和下边的格子是否是障碍格。

如果转移格没有插头,那么就会生成一个新的联通块,处理一下就可以了。

说了这么多,我自己都懵了,还是代码好理解一些

#include
#include
#include
#include
#define SF scanf
#define PF printf
#define MAXNODE 1000100
#define MAXN 22
#define MOD 199999
#define update dp[cur].push(encode()>>3*(j==m),dp[!cur].f[k]);
typedef long long LL;
struct hash{
    int sz,adj[MOD+10],next[MAXNODE];
    LL state[MAXNODE],f[MAXNODE];
    void clr(){
        sz=0;
        memset(adj,-1,sizeof (adj));
    }
    void push(LL s,LL val){
        int u=s%MOD;
        for(int i=adj[u];~i;i=next[i])
            if(state[i]==s){
                f[i]+=val;
                return ;
            }
        state[sz]=s;
        f[sz]=val;
        next[sz]=adj[u];
        adj[u]=sz++;
    }
}dp[2];
int n,m,mat[MAXN][MAXN],ex,ey,cur;
int a[MAXN],num[MAXN];
void decode(LL s){
    for(int i=m;i>=0;i--){
        a[i]=s&7;
        s>>=3;
    }
}
LL encode(){
    memset(num,0,sizeof num);
    int k=0;
    LL ret=0;
    for(int i=0;i<=m;i++){
        if(!num[a[i]]&&a[i])
            num[a[i]]=++k;
        ret=(ret<<3)|num[a[i]];
    }
    return ret;
}
void change(int x,int y){
    for(int i=0;i<=m;i++)
        if(a[i]==x)
            a[i]=y;
}
void dpblank(int i,int j){
    for(int k=0;kstate[k]);
        int &L=a[j-1],&U=a[j];
        if(L&&U){
            if(L==U){
                if(i==ex&&j==ey){
                    L=U=0;
                    update;
                }
            }
            else{
                change(L,U);
                L=U=0;
                update;
            }
        }
        else if(L){
            if(mat[i+1][j])
                dp[cur].push(dp[!cur].state[k]>>3*(j==m),dp[!cur].f[k]);
            if(mat[i][j+1]){
                U=L;
                L=0;
                update;
            }
        }
        else if(U){
            if(mat[i][j+1])
                dp[cur].push(dp[!cur].state[k]>>3*(j==m),dp[!cur].f[k]);
            if(mat[i+1][j]){
                L=U;
                U=0;
                update;
            }
        }
        else{
            if(mat[i][j+1]&&mat[i+1][j]){
                L=U=m+1;
                update;
            }
        }
    }
}
void dpblock(int i,int j){
    for(int k=0;kpush(dp[!cur].state[k]>>(3*(j==m)),dp[!cur].f[k]);
}
int main(){
    char ch;
    SF("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            SF(" %c",&ch);
            if(ch=='.')
                mat[ex=i][ey=j]=1;
            else
                mat[i][j]=0;
        }
    dp[0].clr();
    dp[0].push(0,1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            cur^=1;
            dp[cur].clr();
            if(mat[i][j])
                dpblank(i,j);
            else
                dpblock(i,j);
        }
    LL ans=0;
    for(int i=0;i"%I64d",ans);
}

注释:如果状态中的联通块个数大于m/2,就意味着至少有一个联通块在轮廓线上的插头数量只有1个,那么这个联通块就不会形成回路,所以是不可能出现的情况

你可能感兴趣的:(连通性状态压缩DP)