论插头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 */