• 插头DP


    讲解

    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
    ");
    		return 0;
    	}
    	wrok();
    	printf("%lld
    ",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.
    ",l++,f[n][m][0]);
    	}
    	
    	return 0;
    } 
    

      

  • 相关阅读:
    java基础学习笔记四(异常)
    关于linux下crontab mysql备份出来的数据为0字节的问题
    转:国内优秀npm镜像推荐及使用
    webpack使用总结~
    php下载远程文件方法~
    腾讯开放平台web第三方登录获取信息类(包含签名)
    windows 平台 php_Imagick 拓展遇到的那些坑!
    转:CentOS/Debian/Ubuntu一键安装LAMP(Apache/MySQL/PHP)环境
    composer 报错:Your requirements could not be resolved to an installable set of packages 解决方法
    Javascript模块化编程(三):require.js的用法
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12313357.html
Copyright © 2020-2023  润新知