• 【Ural】1519. Formula 1 插头DP


    【题目】1519. Formula 1

    【题意】给定n*m个方格图,有一些障碍格,求非障碍格的哈密顿回路数量。n,m<=12。

    【算法】插头DP

    【题解】基于连通性状态压缩的动态规划问题》 by CDQ(万恶之源T_T)

    如果你想学最小表示法,当然首推kuangbinの博客

    基本思想是逐格推进,维护轮廓线的m+1个插头的状态,每个插头有一个编号,连通的插头编号相同。

    由于只转移和记录有效状态,所以时空复杂度都大大优于普通的状压DP。

    1.存储:容易发现连通编号至多0~6,所以用数字每三个二进制位存一个插头编号,用hash表存数字,同状态须合并来保证状态总数。

    解码:强制从左到右是从高到低,倒着&7就能解码出数组了。

    2.最小表示法:过程中会出现合并编号和新建编号的操作,通过暴力最小化的方法使得编号位0~6,称为最小表示法。

    编码:最小化的过程体现在编码,用桶vis记老编号对应的新编号,扫一遍即可。新建的编号设为6。

    3.转移:本题的障碍格可以直接不转移,因为插头也不会变化,下面讨论非障碍格的转移(依赖于左插头和上插头)。

    Ⅰ存在左插头和上插头

    如果同编号,那么只有最后一个非障碍格才能闭合,否则状态无效。

    如果不同编号,可以连接,遍历所有插头将和左插头连通的编号改成上插头。

    Ⅱ存在左插头或上插头

    如果右能加插头就转移,如果下能加插头就转移。

    Ⅲ不存在左插头和上插头

    如果右和下都能加插头就转移,插头编号6。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int maxn=15,MOD=30007,S=1000010;
    int c[maxn],map[maxn][maxn],n,m,ex,ey;
    struct h{
        int first[MOD],tot,nxt[S];
        ll state[S],ans[S];
        void init(){
            memset(first,0,sizeof(first));
            tot=0;
        }
        void insert(ll x,ll num){
            for(int i=first[x%MOD];i;i=nxt[i]){
                if(state[i]==x){
                    ans[i]+=num;
                    return;//!!!
                }
            }
            state[++tot]=x;ans[tot]=num;
            nxt[tot]=first[x%MOD];first[x%MOD]=tot;
        }
    }f[2];
    void decode(ll x){
        for(int i=m;i>=0;i--){
            c[i]=x&7;
            x>>=3;
        }
    }
    int vis[maxn];//
    ll encode(){
        for(int i=1;i<=7;i++)vis[i]=0;
        int cnt=0;ll x=0;
        for(int i=0;i<=m;i++){
            if(!c[i]){x<<=3;continue;}
            if(!vis[c[i]])vis[c[i]]=++cnt;
            x=(x<<3)|vis[c[i]];
        }
        return x;
    }
    void solve(int cur,int x,int y){
        for(int k=1;k<=f[cur^1].tot;k++){
            decode(f[cur^1].state[k]);
            int left=c[y-1],up=c[y];
            ll ans=f[cur^1].ans[k];
            if(left&&up){
                if(left==up){
                    if(x==ex&&y==ey){
                        c[y-1]=c[y]=0;
                        f[cur].insert(encode(),ans);
                    }
                }
                else{
                    c[y-1]=c[y]=0;
                    for(int i=0;i<=m;i++)if(c[i]==left)c[i]=up;
                    f[cur].insert(encode(),ans);
                }
            }
            else if(left||up){
                int now=left|up;
                if(map[x+1][y]){
                    c[y-1]=now;c[y]=0;
                    f[cur].insert(encode(),ans);
                }
                if(map[x][y+1]){
                    c[y-1]=0;c[y]=now;
                    f[cur].insert(encode(),ans);
                }
            }
            else{
                if(map[x+1][y]&&map[x][y+1]){
                    c[y-1]=c[y]=7;
                    f[cur].insert(encode(),ans);
                }
            }
        }
    }
    char s[maxn];
    int main(){
        scanf("%d%d",&n,&m);
        memset(map,0,sizeof(map));
        ex=ey=0;
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            for(int j=1;j<=m;j++)if(s[j]=='.'){
                map[i][j]=1;//
                ex=i;ey=j;
            }
            else map[i][j]=0;
        }
        if(!ex){printf("0
    ");return 0;}
        int cur=0;
        f[0].init();f[0].insert(0,1);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(map[i][j])cur^=1,f[cur].init(),solve(cur,i,j);
            }
            for(int j=1;j<=f[cur].tot;j++)f[cur].state[j]>>=3;
        }
        ll ans=0;
        for(int i=1;i<=f[cur].tot;i++)ans+=f[cur].ans[i];
        printf("%lld
    ",ans);
        return 0;
    } 
    View Code
  • 相关阅读:
    linux 常用命令
    git 常见命令
    合并两个有序链表---python
    Code Contract for .NET
    Kruskal最小生成树算法
    逻辑-哲学
    停机问题
    逆向工程
    .net framework
    python 类库
  • 原文地址:https://www.cnblogs.com/onioncyc/p/6685389.html
Copyright © 2020-2023  润新知