论插头dp的写法

题目:https://www.luogu.com.cn/problem/P5056

毕竟现在还不会写,先放几个大佬的代码

1

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=15,M=299989,STA=300000+10;
int n,m,mp[N][N],ex,ey;
int head[STA],nxt[STA];
LL tot[2],f[2][STA],t[2][STA],ans;
int now,las;
void insert(int zt,LL num){
    int x=zt%M+1;
    for(int i=head[x];i;i=nxt[i]){
        if(t[now][i]==zt){
            return;
        }
    }
    nxt[++tot[now]]=head[x];head[x]=tot[now];
    t[now][tot[now]]=zt;f[now][tot[now]]=num;
}
void DP(){
    tot[now]=1;f[now][1]=1;t[now][1]=0;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=tot[now];++j)t[now][j]<<=2;
        for(int j=1;j<=m;++j){
            las=now;now^=1;
            memset(head,0,sizeof(head));tot[now]=0;
            for(int k=1;k<=tot[las];++k){
                int zt=t[las][k];
                int p1=(zt>>(j*2-2))%4,p2=(zt>>(j*2))%4;
                LL num=f[las][k];
                if(!mp[i][j]){
                    if(!p1&&!p2)insert(zt,num);
                }
                else if(!p1&&!p2){
                    if(mp[i+1][j]&&mp[i][j+1])insert(zt+(9<<(j*2-2)),num);
                }
                else if(!p1&&p2){
                    if(mp[i][j+1])insert(zt,num);
                    if(mp[i+1][j])insert(zt+(p2<<(j*2-2))-(p2<<(j*2)),num);
                }
                else if(p1&&!p2){
                    if(mp[i+1][j])insert(zt,num);
                    if(mp[i][j+1])insert(zt-(p1<<(j*2-2))+(p1<<(j*2)),num);
                }
                else if(p1==1&&p2==1){
                    int cnt=1;
                    for(int d=j+1;d<=m;++d){
                        if((zt>>(d*2))%4==1)++cnt;
                        if((zt>>(d*2))%4==2)--cnt;
                        if(!cnt){
                            insert(zt-(5<<(j*2-2))-(1<<(d*2)),num);
                            break;
                        }
                    }
                }
                else if(p1==2&&p2==2){
                    int cnt=1;
                    for(int d=j-2;d>=0;--d){
                        if((zt>>(d*2))%4==1)--cnt;
                        if((zt>>(d*2))%4==2)++cnt;
                        if(!cnt){
                            insert(zt-(10<<(j*2-2))+(1<<(d*2)),num);
                            break;
                        }
                    }
                }
                else if(p1==2&&p2==1)insert(zt-(6<<(j*2-2)),num);
                else if(i==ex&&j==ey)ans+=num;
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    char ch;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            cin>>ch;
            if(ch=='.')mp[i][j]=1,ex=i,ey=j;
            else mp[i][j]=0;
        }
    }
    DP();
    printf("%lld",ans);
    return 0;
}

 2

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
#define Fast_cinios::sync_with_stdio(false),cin.tie();
#define For(i,a,b) for(registerinti=a;i<=b;i++)
#define Forr(i,a,b) for(registerinti=a;i>=b;i--)
#define DEBUG(x) cerr<<"DEBUG"<<x<<">>>"<<endl;
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
template<typename _T>
inline void read(_T&f){
    f=0;_T fu=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')fu=-1;c=getchar();}
    while(c>='0'&&c<='9'){f=(f<<3)+(f<<1)+(c&15);c=getchar();}
    f*=fu;
}
template<typename T>
void print(T x){
    if(x<0)putchar('-'),x=-x;
    if(x<10)putchar(x+48);
    else print(x/10),putchar(x%10+48);
}
template<typename T>
    void print(T x,char t){
    print(x);putchar(t);
}
const int mod=15527,N=mod+5;
ll f[2][N],ans;
int a[15][15],tot[2],head[N],nxt[N],v[2][N],bin[15],n,m,end1,end2,now;
/*
ans->答案
now->滚动数组第一维
f->滚动数组dp,f[now][i]表示的是第i个状态的dp值,第i个状态是hash得来
a->存地图
tot->存某一层的状态数,前向星hash表
nxt&head->hash表
v->状态的4进制数表示,状态是从左到右,从低位到高位记录的,第一个插头是第一个四进制位
bin->4的若干次方,便于从状态中提取第i位的插头
end1&end2->最后一个合法点的坐标,方便统计答案
*/
//ins->对状态进行hash
void ins(int zt,ll val){
    int u=zt%mod;
    for(register int i=head[u];i;i=nxt[i])
        if(v[now][i]==zt){
            f[now][i]+=val;
            return;
        }
    tot[now]++;nxt[tot[now]]=head[u];v[now][tot[now]]=zt;
    head[u]=tot[now];f[now][tot[now]]=val;
}
void sol(){
    tot[now]=1;f[now][1]=1;v[now][1]=0;
    for(register int i=1;i<=n;i++){
        for(register int j=1;j<=tot[now];j++)v[now][j]<<=2;
        for(register int j=1;j<=m;j++){
            now^=1;memset(head,0,sizeof(head));tot[now]=0;//清空hash表
            //枚举从上一层dp中的哪一个状态转移过来
            for(register int k=1;k<=tot[now^1];k++){
                int zt=v[now^1][k];ll val=f[now^1][k];
                //提取关键的两个插头的状态,即(i,j-1)到(i,j)的插头和(i-1,j)到(i,j)的插头
                int t1=(zt>>((j<<1)-2))&3,t2=(zt>>(j<<1))&3;
                //                fprintf(stderr,"zt=%d,t1=%d,t2=%d\n",zt,t1,t2);
                //情况1,(i,j)这个点不能走
                if(a[i][j]==0){
                    //不能有过来的插头,关键的两个插头都为0,即两个方格都没有过来的边,才能转移
                    if(t1==0&&t2==0)ins(zt,val);
                    continue;
                }
                //(i,j)一定要走
                //情况2,两个关键插头都为0
                    if(t1==0&&t2==0){
                    //因为(i,j)一定要走,所以放上一个(i,j)到(i+1,j)且(i,j)到(i,j+1)的插头
                    //需要判断(i+1,j)和(i,j+1)是否能走
                    if(a[i+1][j]==1&&a[i][j+1]==1)ins(zt^bin[j-1]^(bin[j]<<1),val);
                }else if(t1==0&&t2!=0){
                    if(a[i][j+1]==1)ins(zt,val);
                    if(a[i+1][j]==1)ins(zt^(bin[j-1]*t2)^(bin[j]*t2),val);
                }else if(t1!=0&&t2==0){
                    if(a[i][j+1]==1)ins(zt^(bin[j-1]*t1)^(bin[j]*t1),val);
                    if(a[i+1][j]==1)ins(zt,val);
                }else if(t1==1&&t2==1){
                    //需要找到一个2插头来匹配,for循环暴力找,但是中间的12括号要匹配
                    int nowv=1;//1插头的个数
                    //这里题解写的是t<=m,但画了图发现好像t<m就可以了,希望以后不要错
                    for(register int t=j+1;t<m;t++){
                        int t3=(zt>>(t<<1))&3;
                        if(t3==1)nowv++;
                        if(t3==2)nowv--;
                        if(!nowv){ins(zt^(bin[t]*3)^bin[j-1]^bin[j],val);break;}
                    }
                }else if(t1==2&&t2==2){
                    int nowv=1;//2插头的个数
                    //这里题解写的是t>=0,但根据同理可得发现好像t>0就可以了,希望以后不要错
                    for(register int t=j-2;t>0;t--){
                    int t3=(zt>>(t<<1))&3;
                    if(t3==1)nowv--;
                    if(t3==2)nowv++;
                    if(!nowv){ins(zt^(bin[t]*3)^(bin[j-1]<<1)^(bin[j]<<1),val);break;}
                    }
                }else if(t1==2&&t2==1)ins(zt^(bin[j-1]<<1)^bin[j],val);
                else if(end1==i&&end2==j)ans+=val;
            }
        }
    }
}
int main(){
    read(n);read(m);
    for(register int i=1;i<=n;i++){
        for(register int j=1;j<=m;j++){
        char c=getchar();
        while(c!='.'&&c!='*')c=getchar();
        if(c=='.')a[i][j]=1,end1=i,end2=j;
        }
    }
    bin[0]=1;
    for(register int i=1;i<=m;i++)bin[i]=bin[i-1]<<2;
    sol();print(ans,'\n');
    return 0;
}
/*

插头的状态有0,1,2三种,其中0表示这个地方没有插头,1和2是根据插头的x坐标进行区分的,在左边的是1,右边的是2
参考:https://blog.csdn.net/litble/article/details/79369147

*/

 

posted @ 2020-07-27 16:49  Nakiri_Ayame_suki  阅读(402)  评论(0编辑  收藏  举报