一、计数问题 (数位统计DP)
求一个区间所有数的各位中,中1~9各自出现的次数。
如果要求1-xxxxxxx中第四位中1出现的次数,思想是分情况讨论(划分集合):
假设数为:1 <= xxx1xxx<=abcdefg
1、若前三位xxx== (0,abc-1) ,yyy=0~999 都行 :abc*1000
2、若xxx == abc
2.1 当 d<1 的时候: 0
2.2 当 d==1 的时候:0-efg ,efg+1种
2.3 当 d>1的时候,0-999, 1000种
二、状态压缩DP 291、蒙德里安的梦想:
思路:当所有的横向摆放的情况固定之后,竖向也已经固定,只要求出所有横向摆放的方式数。
状态表示:f[ i , j ]表示第i列的状态为j(从i-1列捅过来的)的时候的方案数;
界限是:从0到m,为什么要让f[0,0]=1,因为我们的思路是,j
关于为什么要将f[0,0]=1;要注意:我们要放的是从0~m-1列,而只有将其设置为1,我们在m==2的时候,才能无论j是什么状态,都可以成功加1。
为什么要计算f(m,0):
#include<bits/stdc++.h>
using namespace std;
const int N=12,M=1<<12;
long long f[N][M];
bool st[N];//用于判断i处于状态j的时候,i-1列的状态是否满足
int main()
{
int n,m;
while(cin>>n>>m ,n||m)
{
memset(f,0,sizeof(f));
for(int i=0;i<1<<n;i++)
{
st[i]=true;
int cnt=0;
for(int j=0;j<n;j++)
{
if(i>>j&1)
{
if(cnt&1)st[i]=false,cnt=0;
}
else cnt++;
}
//最后肯状态的后几个数都是0,需要进行判断
if(cnt&1)st[i]=false;
}
f[0][0]=1;
for(int i=1;i<=m;i++)
{
for(int j=0;j<1<<n;j++)
{
for(int k=0;k<1<<n;k++)
{
if((j&k)==0&&st[j|k])
{
f[i][j]+=f[i-1][k];
}
}
}
}
cout<<f[m][0]<<endl;
}
return 0;
}
三、最短Hamilton路径(状态压缩DP)
不重不漏走过所有点的最短路径。
状态表示:i为从0到j走过的所有点的二进制。f值为其最小的路径长度。
状态划分,目前路径的倒数第二个数是谁。
#include<bits/stdc++.h>
using namespace std;
//91. 最短Hamilton路径
const int N=20,M=1<<N;
int n;
int w[N][N];
int f[M][N];
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cin>>w[i][j];
}
}
memset(f,0x3f,sizeof(f));
//起点为0,到0的距离就是0
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)
f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);
}
}
}
}
cout<<f[(1<<n)-1][n-1]<<endl;
}