状态压缩动态规划——最小总代价

状态压缩

一个包含n个元素的集合,该集合的子集可以表示为从 0 0 0~ 2 n − 1 2^n - 1 2n1的是一个十进制整数。 例如:一共有5个人,第0、3个人被选择,就用 ( 01001 ) 2 (01001)_2 (01001)2表示,那么该子集对应的十进制数为9。此谓状态压缩。

算法思想

状态压缩

把传递过物品的人的集合压缩为一个整数i,用i的二进制表示这个集合。例如:一共有5个人,第0、3个人被传递过,就用 ( 01001 ) 2 (01001)_2 (01001)2表示,该集合的十进制表示为9。

状态表示

f[i][j]表示当前传递过物品的人的集合为i、最后一个传递到的人为j的情况下的最小代价。

状态转移

因为j是最后一个被传递到的人,也就是说j必须包含在集合i中,那么合法状态必须满足(i >> j) & 1 != 0

如果能从j转移到下一个人k,那k必须不在集合i中,那么必须满足(i >> k) & 1 == 0

转移方程为f[i | 1 << k][k] = min(f[i | 1 << k][k], f[i][j] + g[j][k])

初始状态

因为开始时物品可以在任意一人手上,此时最小代价为0,所以f[1 << i][i] = 0,其它状态为无穷大

时间复杂度

O ( n × n × 2 n ) O(n×n×2^n) O(n×n×2n)

状态数: n × 2 n n×2^n n×2n
转移次数: n n n

代码实现

#include 
#include 
using namespace std;

const int N = 20, M = 1 << N, INF = 0x3f3f3f3f;

int f[M][N];
int g[N][N];
int n;
int main()
{
    cin >> n;
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++)
            cin >> g[i][j];

    //初始化f[i][j]
    memset(f, 0x3f, sizeof f);
    for(int i = 0; i < n; i ++)
        f[1 << i][i] = 0; //开始时,物品可以在任何人手里

    //遍历所有集合
    for(int i = 0; i < 1 << n; i ++)
    {
        //遍历当前阶段的最后一个人的编号
        for(int j = 0; j < n; j ++)
        {
            if(i >> j & 1) //如果当前集合包含编号为j的人,则认为是合法状态
            {
                for(int k = 0; k < n; k ++)
                {
                    if((i >> k & 1) == 0) //如果当前集合不包含编号为k的人,才能进行转移
                        f[i | 1 << k][k] = min(f[i | 1 << k][k], f[i][j] + g[j][k]);
                }
            }
        }
    }

    int res = INF;

    //打擂台求所有人的传递过时最小值
    for(int i = 0; i < n; i ++)
        res = min(res, f[(1 << n) - 1][i]);

    cout << res << endl;

    return 0;
}

你可能感兴趣的:(动态规划)