【题解】蒙德里安的梦想/最短哈密顿路径

一、AcWing 291. 蒙德里安的梦想

求把 N×M 的棋盘分割成若干个 1×2的长方形,有多少种方案。

例如当 N=2,M=4时,共有 5 种方案。当 N=2,M=3 时,共有 3种方案。

如下图所示:
在这里插入图片描述

1.1问题分析
我们把这个切割问题转化成为摆长方形的问题,1x2的长方形,既可以竖着摆,也可以横着摆。我们只用分析出哪些位置横着摆,如果剩下的位置就只能竖着摆,则该答案为可行解。

  1. 首先,如何确定位置是否已经被摆放?引入01变量的就行了,学过数学建模的同学都知道吧。

状压dp的建模方式往往是引入0/1变量

  1. 如何枚举每种情况?既然都是01整数规划了,肯定用位运算啊(),即1<

咳咳,回到本题,首先我们要确保每个横向摆放的矩形之间的纵向举例为偶数,此处是纵向枚举。
横向枚举时,保证原本横着摆的矩形不重叠。

嗯?上述操作中前者可以数据预处理吧,那就先预处理吧,多点常数,时间复杂度确降低了。

1.2模型建立
这题用DP就行了, f [ i ] [ j ] f[i][j] f[i][j]为表示从i-1列伸到i列,且所有行状态为j的状态,
集合划分的依据是所有i-2列伸到i-1列,且所有行状态为k的状态。
对于k行而言,如果合法,则可以累加,合法条件上面已经提到了。
即:
f [ i ] [ j ] + = f [ i − 1 ] [ k ] ; f[i][j]+=f[i-1][k]; f[i][j]+=f[i1][k];

此处第二维表示的是状态,故叫作状压DP。

1.3模型求解
DP问题初始化一直是个难点,上述状态转移方程可能会使 i = 0 i=0 i=0(越界状态),我们不妨初始化 i = 0 i=0 i=0时f的值。这里 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1,表示只能横着摆,这样其他所有状态都可以递推出来了~
预处理的过程是一个模拟的过程,所以要考虑周全,特别是边界问题。

#include 
using namespace std;
const int maxn=12,maxm=1<<12;
typedef long long LL;
int n,m;
LL f[maxn][maxm];  //f[i][j]表示从i-1列伸到i列,且所有行状态为j的状态
int mark[maxm];

inline bool is_odd(int num)
{
    return num%2==1;
}
/*
集合的划分依据就是所有i-2列伸到i-1列,且所有行状态为k的状态
*/
int main()
{
    while(cin>>n>>m &&((n!=0)&&(m!=0)))
    {
        memset(mark,0,sizeof(mark));
        for(int i=0;i<1 << n;i++)    //枚举每种状态,判断是否会发生冲突(模拟)
        {
            int cnt=0;
            for(int k=0;k<n;k++)
            {
                if(i>>k & 1 ==1)
                {
                    if(is_odd(cnt))
                    {
                        mark[i]=true; //非法
                        break;
                    }
                }
                else
                    cnt++;
            }
            if(is_odd(cnt)) mark[i]=true;
        }

        memset(f,0,sizeof(f));
        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(!(k&j) && !(mark[k|j]))  //在合法的条件下进行状态转移
                        f[i][j]+=f[i-1][k];
                }
            }
        }
        cout<<f[m][0]<<endl;
    }
    return 0;
}

二、AcWing 91. 最短Hamilton路径

给定一张 n个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1的最短 Hamilton 路径。
Hamilton 路径的定义是从 0到 n−1 不重不漏地经过每个点恰好一次。

本题特性:
每个点是否经过就是一个01变量,即n个点的01变量的组合为i, f [ i ] [ j ] f[i][j] f[i][j]表示经过j个点时已然经过的路线状态为i。

2.1模型建立
然后就是一个集合的状压dp,当然,本质上还是一个01规划问题。
f [ i ] [ j ] = m i n { f [ i − { k } ] [ k ] + w [ k ] [ j ] , f [ i ] [ j ] } f[i][j]=min\{f[i-\{k\}][k]+w[k][j],f[i][j]\} f[i][j]=min{f[i{k}][k]+w[k][j],f[i][j]}

2.2模型求解
1、枚举的顺序别搞错了,先枚举第一维,再枚举第二维。根据本题的特性,第一维是枚举状态。
2、位运算枚举搜索顺序

#include 
using namespace std;
const int maxn=21;
const int maxm=1<<21;
int f[maxm][maxn];
int g[maxn][maxn];
int main()
{
    int n;
    cin>>n;
    memset(f,0x3f,sizeof(f));
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
            cin>>g[i][j];
    }
    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>>k&1)
                    {
                        f[i][j]=min(f[i][j],f[i-(1<<j)][k]+g[k][j]);  
                    }
                }
            }
        }
    }
    cout<<f[(1<<n)-1][n-1];
    return 0;
}

你可能感兴趣的:(算法竞赛,动态规划,算法)