讲解
https://www.luogu.com.cn/problemnew/solution/P5056
https://blog.csdn.net/litble/article/details/79369147
https://blog.csdn.net/zhangjianjunab/article/details/80987531
例1、题目大意:一个网格图中有若干障碍格子,求其他格子的哈密尔顿回路总数
只能有一个回路,有多少种可能
//我觉得这个写法比较好理解
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int mod=200000;//hash表大小,因为hash表有压缩功能,所以一般100万就够了
int map[50][50]={0};
int n,m,ex=-1,ey=-1; //最后一个非障碍格子的坐标
char ss[210];
LL ans=0;
int now,php;
struct node{
int hash[mod]; //压缩状态
int key[mod]; //记录原本的数值
int size; // 记录存了多少次数
LL num[mod]; // 记录当前数值代表了多少状态
void mem(){
memset(hash,-1,sizeof(hash));
size=0; //初始化函数
}
void add(int S,int sum){
int s=S%mod;
while(hash[s]!=-1&&key[hash[s]]!=S){
s++;
s%=mod;
}
//判断重复,保证下一次同样一个数也可以到达这个hash值
if(hash[s]==-1){
hash[s]=++size; //压缩状态
key[size]=S; //存储原本的数值
num[size]=sum; //代表了多少状态
} //新建一个hash格
else num[hash[s]]+=sum; //有的话直接加
}
}dp[2]; //滚动数组
int set(int s,int p){
return (s>>((p-1)*2))&3; //取出第p个位置的数
}
void change(int &s,int p,int v){
s^=set(s,p)<<((p-1)*2);
s^=(v&3)<<((p-1)*2); //改变第p位上的数位v
}
void wrok(){
LL sum=0;
now=0,php=1;
dp[now].mem();
dp[now].add(0,1); //初始化滚动型DP数组
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
swap(now,php); //使用滚动数组
dp[now].mem();
for(int k=1;k<=dp[php].size;k++){
int s=dp[php].key[k];
sum=dp[php].num[k];
int q=set(s,j); //右插头
int p=set(s,j+1); //下插头
if(map[i][j]==0){ //障碍格子 ,不能有插头
if(p==0&&q==0) dp[now].add(s,sum);
continue;
}
if(q==0&&p==0){ //|一 没人指我,只好自己建一个插头
if(map[i+1][j]==1&&map[i][j+1]==1){ //判断是不是障碍格子
change(s,j,1); //
change(s,j+1,2);//从这个格子建两个插头
dp[now].add(s,sum);
}
}
else if(q>0&&p==0){ //两种方式
if(map[i+1][j]==1) dp[now].add(s,sum); //不会改变
if(map[i][j+1]==1){ //把插头指向右边,继承下去
change(s,j,0);
change(s,j+1,q); //相当于交换这两列
dp[now].add(s,sum);
}
}
else if(q==0&&p>0){ //跟上面一样,两种方式
if(map[i][j+1]==1) dp[now].add(s,sum);
if(map[i+1][j]==1){
change(s,j,p);
change(s,j+1,0);
dp[now].add(s,sum);
}
}
else if(q==1&&p==1){
int find=1; //算上p为1!很重要。
for(int tt=j+2;tt<=m;tt++){ //找到与p相对的右插头并修改为这一大块插头的左插头
int vs=set(s,tt);
if(vs==1) find++;
else if(vs==2) find--;////为什么可以?因为中间罩住的插头不会出去这个大插头的范围,所以只有碰到与p成对的插头才会清零
if(find==0){
change(s,j,0);
change(s,j+1,0);
change(s,tt,1);
dp[now].add(s,sum);
break;
}
}
}
else if(q==2&&p==2){
int find=1;
for(int tt=j-1;tt>=1;tt--){ ///找到与q相对的左插头并修改为这一大块插头的右插头
int vs=set(s,tt);
if(vs==2) find++;
else if(vs==1) find--;
if(find==0){
change(s,j,0);
change(s,j+1,0);
change(s,tt,2);
dp[now].add(s,sum);
break;
}
}
}
else if(q==2&&p==1){ //这样配对的插头刚好是我们想要的
change(s,j,0);
change(s,j+1,0); //直接抵消清零就可以了
dp[now].add(s,sum);
}
else if(q==1&&p==2){
if(ex==i&&ey==j) ans+=sum;//得到答案
}
}
}
for(int j=1;j<=dp[now].size;j++) dp[now].key[j]<<=2;
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",ss+1);
for(int j=1;j<=m;j++){
if(ss[j]=='.'){
map[i][j]=1;
ex=i;
ey=j;
}
}
}
if(ex==-1) {
printf("0\n");
return 0;
}
wrok();
printf("%lld\n",ans);
return 0;
}
例2、有多个回路,有多少种可能
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n,m;
int bin[15],mp[15][15];
LL f[12][12][(1<<12)];
int t;
int l=1;
//这道题的回路数目不限,所以只需要记录有没有插头的状态,
int main(){
scanf("%d",&t);
bin[0]=1; //预处理
for(int i=1;i<=12;i++) bin[i]=bin[i-1]<<1;
while(t--){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) scanf("%d",&mp[i][j]);
}
memset(f,0,sizeof(f)); //每次都清零
f[0][m][0]=1;
int lim=bin[m+1]-1; //状态数
for(int i=1;i<=n;i++){
for(int zt=0;zt<=lim;zt++) f[i][0][zt<<1]=f[i-1][m][zt]; ///!!!!
for(int j=1;j<=m;j++)
for(int zt=0;zt<=lim;zt++){
int b1=zt&bin[j-1];
int b2=zt&bin[j];
LL num=f[i][j-1][zt];
if(!mp[i][j]){
if(!b1&&!b2) f[i][j][zt]+=num;
continue;
}
if(!b1&&!b2){
if(mp[i][j+1]&&mp[i+1][j]) f[i][j][zt+bin[j-1]+bin[j]]+=num;
}
else if(!b1&&b2){
if(mp[i][j+1]) f[i][j][zt]+=num;
if(mp[i+1][j]) f[i][j][zt-bin[j]+bin[j-1]]+=num;
}
else if(b1&&!b2){
if(mp[i][j+1]) f[i][j][zt-bin[j-1]+bin[j]]+=num;
if(mp[i+1][j]) f[i][j][zt]+=num;
}
else f[i][j][zt-bin[j-1]-bin[j]]+=num;
}
}
printf("Case %d: There are %lld ways to eat the trees.\n",l++,f[n][m][0]);
}
return 0;
}
posted on
浙公网安备 33010602011771号