【算法基础23】数位DP与状态压缩DP(计数问题、蒙德里安的梦想、最短hamilton路径)

一、计数问题(数位DP)

        问题描述:给定两个数字a和b,求这两个数之间的所有数中各个数字出现的次数。

        问题分析:题目区间[ a , b ]两端都是未知数字,可以转化为求区间[ 1 , a ]和区间[ 1 , b ],再利用前缀和思想作差,这样就能将问题化简为一端是未知数字。在统计数字 x 在区间[ 1 , n ]中数字出现次数时,可以按出现的位次进行分情况讨论,示例如图。此外还需注意边界问题,按从左往右位序分情况时,由于前导零不占位序,统计0出现的次数时前半部分不能全为0,最小从001开始。

 

        代码:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int get(vector<int> num,int l,int r){//由位序求数字
	int res=0;
	for(int i=l;i>=r;i--)
		res=res*10+num[i];
	return res;
}

int power10(int x){//求位序权重
	int res=1;
	while(x--) res*=10;
	return res;
}

int count(int n,int x){
	if(!n) return 0;
	
	vector<int> num;
	while(n){
		num.push_back(n%10);//按位拆解数字
		n/=10;
	}
	n=num.size();
	
	int res=0;	
	for(int i=n-1-!x;i>=0;i--){//按位遍历,注意x=0时没有求前半部分
		if(i<n-1){//求前半部分
			res+=get(num,n-1,i+1)*power10(i);
			if(!x) res-=power10(i);//x=0时要从001开始
		}		
		if(num[i]==x) res+=get(num,i-1,0)+1;//求后半部分
		else if(num[i]>x) res+=power10(i);
	}
	
	return res;
}

int main(){
	int a,b;
	while(cin>>a>>b,a||b){
		if(a>b) swap(a,b);
		
		for(int i=0;i<10;i++){
			cout<<count(b,i)-count(a-1,i)<<' ';//利用前缀和思想做差
		}
		cout<<endl;
	}
	
	return 0;
}

        输出示例:

 

二、蒙德里安的梦想(状态压缩DP)

        题目要求:

        题目分析:f[ i , j ]表示第 i 列在 j 情况( j 是一个2进制数,从左往右数第 x 位为1代表第 x 行与前一列一起构成了横向长方形)下可以划分长方形的方案数。从f[ i-1 ,  j ]转移到f[ i , j ]的划分方案 k 要注意两个条件:① k & j  == 0,不为0时代表当前行的 i-1 列已经有了长方形尾,第 i 列不可能出现长方形尾。② j | k 不能出现连续的奇数个0,若出现无法用竖着的长方形填满。

        代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1010;
int f[N][N];
bool st[N];

int main(){
	int n,m;
	
	while(cin>>n>>m,n||m){
		memset(f,0,sizeof f);
		
		for(int i=0;i<1<<n;i++){//提取遍历所有k的取值,找出连续奇数个0的方案
			st[i]=true;
			int cnt=0;//记录连续0的个数
			for(int j=0;j<n;j++){//遍历此方案的每一位
				if(i>>j&1){//连续的0中断了
					if(cnt&1) st[i]=false;//连续0的个数是奇数
					cnt=0;
				}
				else cnt++;				
			}
			if(cnt&1) st[i]=false;//处理最后一段连续的0
		}
		
		f[0][0]=1;
		for(int i=1;i<=m;i++){//遍历列
			for(int j=0;j<1<<n;j++){//遍历j
				for(int k=0;k<1<<n;k++){//遍历可能方案K
					if((j&k)==0&&st[j|k]){//此方案可行
						f[i][j]+=f[i-1][k];
					}
				}
			}
		}
		
		cout<<f[m][0]<<endl; 
	}
	return 0;
}

        输出示例:

三、最长hamilton路径(状态压缩DP)

        问题描述:给定一张n个结点的带权无向图,求起点0到终点n-1的最短hamilton路径。(从0到n-1不重不漏地经过每个点恰好一次。)

        问题分析:用f[ i , j ]表示从0到 j 的一条路径 i (二进制数,从右往左数第 x 位为 1 代表经过编号为 x 的点)的最短长度。按照路径倒数第二个点划分 n 种情况,则f[ i , j ] = min ( f[ i-{k} ,k] + a[ k , j ] )。

        代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=20,M=1<<N;
int f[M][N];
int a[N][N];

int main(){
	int n;
	cin>>n;
	
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			cin>>a[i][j];//存储无向图权重
			
	memset(f,0x3f,sizeof f);//初始化
	f[1][0]=0;
	
	for(int i=0;i<1<<n;i++)//遍历所有可能路径
		for(int j=0;j<n;j++)//遍历不同终点
			if(i>>j&1){//该路径能达到该终点
				for(int k=0;k<n;k++){//遍历倒数第二个点
					if((i-(1<<j))>>k&1)//该路径能到达k点且k点与j点不为同一点
						f[i][j]=min(f[i][j],f[i-(1<<j)][k]+a[k][j]);//找到最短路径
				}
			}
			
	cout<<f[(1<<n)-1][n-1]<<endl;
	return 0;
}

        输出示例:

参考资料:acwing

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值