状态压缩dp入门题目总结——炮兵阵地和TSP问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27008079/article/details/61635423

Corn Fields

POJ3254 题目链接
Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares are infertile and can’t be planted. Canny FJ knows that the cows dislike eating close to each other, so when choosing which squares to plant, he avoids choosing squares that are adjacent; no two chosen squares share an edge. He has not yet made the final choice as to which squares to plant.

Being a very open-minded man, Farmer John wants to consider all possible options for how to choose the squares for planting. He is so open-minded that he considers choosing no squares as a valid option! Please help Farmer John determine the number of ways he can choose the squares to plant.

【题目大意】:
M*N的土地,种植农作物,其中某些土地不能种植,相邻土地之间不能种植,问有多少种种植方案。

【题目思路】:
因为M ϵ [1, 12],可将这一行的种植状态用二进制数字表示,1表示种植,0表示不种植,二进制数范围为[0, 1<<12)。

这一行相邻土地不能种植可以通过M&(M<<1)是否为1来判断。
上一层土地状态S1和这一层土地状态S2 通过S1&S2是否为0表示合法。
map[i]表示i层土地可以种植的情况,通过(~map[i])&S是否为0表示这一层是否合法。

定义dp[i][S] 表示目前i行状态为S时的种植方案数。
dp[i][S]+=dp[i1][S]

【程序代码】

#include<iostream>
using namespace std;
int m, n;
int map[13];
const int mod = 100000000;
int dp[13][100000];     //dp[i][j] 放第i行 这一行状态为j  时方法数 

bool check(int x, int y)
{
    if(x&(x<<1)){   //这一行相邻放牛了 
        return false;
    }

    if((~y)&x){        //这一行不能放牛的位置上放牛了 
        return false;
    }
    return true;
}

int main()
{
    int i, j, k;
    cin >> m >> n;
    for(i=1; i<=m; i++)
    {
        for(j=1; j<=n; j++)
        {
            int x;
            cin >> x;
            map[i] = map[i]*2+x;
        }
    }

    for(i=0; i<(1<<n); i++){
        if(check(i, map[1]))
        {
            dp[1][i] = 1;
        }
    }

    for(i=2; i<=m; i++)
    {
        for(j=0; j<=(1<<n)-1; j++)
        {
            if(!check(j, map[i]))
            {
                continue;
            }

            for(k=0; k<=(1<<n)-1; k++)
            {
                if(!(j&k))
                {
                    dp[i][j] += dp[i-1][k];
                    dp[i][j] %= mod;
                }
            }
        }
    }

    int ans = 0;
    for(i=0; i<(1<<n); i++)
    {
        ans += dp[m][i];
        ans %= mod;
    }

    cout << ans << endl;
    return 0;
 } 

炮兵阵地

POJ1185 题目链接

司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用”H” 表示),也可能是平原(用”P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

【题目思路】
比上一题限制多一些,这一次不仅是相邻了,相互之间两格之间也算,这一层的状态不仅依赖于上一层状态,而且依赖于上上层状态,对于上上层状态时,在枚举中不仅要与这一层状态对比,还要和上一层状态对比来确定是否合法。
因此定义数组dp[i][j][k] 表示前i层,第i层状态为j ,第i-1层状态为k 的最多数量 ,因为限制增加,所以每一层可行的状态数减少了。
一行的状态是否合法通过i&(i<<1)和i&(1<<2)同时为0满足。
状态转移方程: dp[i][j][k]=max(dp[i1][k][l])

【程序代码】

#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
struct state
{
    int val;
    int num;
};
vector<state> v;
int map[101];
int dp[101][100][100];  //dp[i][j][k] 前i层,第i层状态为j ,第i-1层状态为k 的最多数量 

int work(int x) //统计数里1个数 
{
    int s = 0;
    while(x > 0)
    {
        if(x % 2 == 1){
            s++;
        }
        x /= 2;
    }
    return s;   
}

int main()
{
    int n, m;
    int i, j, k, l;
    cin >> n >> m;
    getchar();
    for(i=1; i<=n; i++)
    {
        char c;
        int val;
        for(j=1; j<=m; j++)
        {
            cin >> c;
            if(c == 'P'){
                val = 0;
            }
            else{
                val = 1;
            }

            map[i] = map[i]*2 + val;
        }
        getchar();
    }

    for(i=0; i<(1<<m); i++)     //行满足条件的状态 
    {
        if((i&(i<<1)) == 0 && (i&(i<<2)) == 0)
        {
            state s;
            s.val = i;
            s.num = work(i);
            v.push_back(s);
        }
    }

    memset(dp, -1, sizeof(dp));
    for(i=0; i<v.size(); i++)
    for(j=0; j<v.size(); j++)
    {
        if((map[1] & v[i].val) == 0){
            dp[1][i][j] = v[i].num;
        }
    }

    for(i=2; i<=n; i++)
    {
        for(j=0; j<v.size(); j++)
        {
            state sj = v[j];

            if((sj.val & map[i]) == 0)
            {
                for(k=0; k<v.size(); k++)
                {
                    state sk = v[k];

                    if((sj.val & sk.val) != 0){
                        continue;
                    }

                    for(l=0; l<v.size(); l++)
                    {
                        state sl = v[l];
                        if((sj.val & sl.val) != 0)
                        {
                            continue;
                        }

                        dp[i][j][k] = max(dp[i][j][k], dp[i-1][k][l]);
                    }

                    if(dp[i][j][k] != -1){
                        dp[i][j][k] += sj.num;
                    }


                }

            }
        }
    }

    int ans = 0;
    for(i=0; i<v.size(); i++)
    for(j=0; j<v.size(); j++)
    {
        if(ans < dp[n][i][j])
        {
            ans = dp[n][i][j];
        }
    }

    cout << ans << endl;
    return 0;
 } 

Hie with the Pie

POJ3311 题目链接
The Pizazz Pizzeria prides itself in delivering pizzas to its customers as fast as possible. Unfortunately, due to cutbacks, they can afford to hire only one driver to do the deliveries. He will wait for 1 or more (up to 10) orders to be processed before he starts any deliveries. Needless to say, he would like to take the shortest route in delivering these goodies and returning to the pizzeria, even if it means passing the same location(s) or the pizzeria more than once on the way. He has commissioned you to write a program to help him.

【题目大意】从0节点经过n个节点最后返回0节点,其中节点可访问不只一次,问需要的最短时间。

【题目思路】
节点不限制访问次数的TSP问题,也就是说从u节点到v节点可能进过k节点比直接到v节点更快,所以先要求出各个节点之间的最短路,用Floyd算法。

for(k=0; k<=n; k++)
        {
            for(i=0; i<=n; i++)
            {
                for(j=0; j<=n; j++)
                {
                    if(dist[i][j] > dist[i][k] + dist[k][j])
                    {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }

接着采用TSP问题处理方式,定义dp[i][j]目前处于i节点 j状态 到目标的最小时间,j状态通过各个节点是否访问来定义。
状态转移方程: dp[i][j]=min(dp[k][j|(1<<k)]+dict[i][k])

【程序代码】

#include<iostream>
using namespace std;
int dist[11][11];
int dp[11][1<<11];  //dp[i][j]目前处于i节点 j状态 到目标的最小 

int main()
{
    int n;
    int i, j, k;
    while(1)
    {
        cin >> n;
        if(n == 0){
            break;
        }

        for(i=0; i<=n; i++)
        {
            for(j=0; j<=n; j++)
            {
                cin >> dist[i][j];
            }
        }

        for(k=0; k<=n; k++)
        {
            for(i=0; i<=n; i++)
            {
                for(j=0; j<=n; j++)
                {
                    if(dist[i][j] > dist[i][k] + dist[k][j])
                    {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }

        for(i=0; i<=n; i++){
            for(j=0; j<=(1<<n+1)-1; j++)
            {
                dp[i][j] = 99999999;
            }
        }
        dp[0][(1<<n+1)-1] = 0;

        for(k=(1<<n+1)-2; k>=0; k--)
        {
            for(i=0; i<=n; i++)
            {
                if((k!=0)&&((k&(1<<i)) == 0) || (k==0&&i!=0)){  //当前状态下可以作为目前的节点 
                    continue;
                }

                for(j=0; j<=n; j++)
                {
                    if((k&(1<<j)) == 0)
                    {
                        dp[i][k] = min(dp[i][k], dp[j][k|(1<<j)] + dist[i][j]);
                    }
                }
            }
        }

        cout << dp[0][0] << endl;
    }

    return 0;
}

Traveling by Stagecoach

POJ2686 题目链接

【题目大意】
从A城市到B城市,中间经过的城市路途上需要马车,现在有n张车票,每张上有马的匹数,从x到y需要的时间为路程/马匹数,问所需最短时间,或者不能到达。

【题目思路】
和TSP问题基本一致

#include<cstring>
#include<iostream>
using namespace std;
const int INF = 9999999;
int n, m, p, a, b;
int array[31][31];
int t[9];
double dp[31][1<<8];
int vis[31];

double solve(int x, int y)
{
    if(x == b && y >= 0){
        return dp[b][y] = 0;
    }

    if(dp[x][y] > 0){
        return dp[x][y];
    }

    vis[x] = 1;
    int i, j;
    dp[x][y] = INF;
    if(y <= 0){
        return dp[x][y];
    }

    for(i=1; i<=m; i++)
    {
        if(array[x][i] && !vis[i])
        {
            for(j=0; j<n; j++)
            {
                if(y >> j & 1){
                    dp[x][y] = min(dp[x][y], solve(i, y & ~(1 << j)) + array[x][i] * 1.0/ t[j]);
                    vis[i] = 0;
                }
            }
        }

    }

    return dp[x][y];
}
int main()
{
    int i, j, k;
    while(1){
        cin >> n >> m >> p >> a >> b;
        if(n == 0 && m == 0 && p == 0 && a == 0 && b == 0){
            break;
        }
        for(i=0; i<n; i++){
            cin >> t[i];
        }
        memset(array, 0, sizeof(array));
        for(i=1; i<=p; i++){
            int x, y, z;
            cin >> x >> y >> z;
            array[x][y] = array[x][y] > 0 ? min(z, array[x][y]) : z;
            array[y][x] = array[y][x] > 0 ? min(z, array[y][x]) : z;
        }

        memset(dp, 0, sizeof(dp));
        memset(vis, 0, sizeof(vis));

        double ans = solve(a, (1 << n)- 1);
        if(ans ==INF){
            cout << "Impossible\n";
        }
        else{
            printf("%.5lf\n", ans);
        }

    }
 } 

Travelling

hdu3001 题目链接

【题目大意】
要走访n个点,起始点任意,任意一个点最多2次,问最短路.

【题目思路】
定义dp[i][j] 处于i点 j状态到满足目标的最短路 ,这次状态定义用三进制数表示,这样可以表示出一个节点最多访问2次。
用记忆化搜索比较习惯,初始时可从0节点开始,0节点和每个节点都有边,用来表示出真实的起始点任意。和普通的TSP做法差不多。

//要走访n个点,起始点任意,任意一个点最多2次,问最短路. 
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[11][60000];  //dp[i][j] 处于i点 j状态到满足目标的最短路 
int n, m;
int dict[101][101];
const int INF = 99999999;

bool test(int s)    //判断s状态是不是每个点都访问了 
{
    int sum = 0;
    while(s > 0)
    {
        if(s % 3 == 0)
        {
            return false;   
        }   
        s /= 3;
        sum++;
    }   

    if(sum < n)
    {
        return false;
    }
    return true;
 } 

int work(int s, int j)  求3^j 
{
    int sum = 1;
    int i;
    for(i=1; i<=j; i++)
    {
        sum *= s;   
    }   
    return sum;
}

int get(int s, int k)   //求三进制中第k位上数字(从后往前) 
{
    int sum = 0;
    while(s > 0){
        sum++;
        if(sum == k)
        {
            return (s % 3);
        }
        s /= 3;
    }   

    return 0;
 } 

int dfs(int k, int s)
{
    if(test(s))
    {
        return 0;   
    }   

    if(dp[k][s] != -1){
        return dp[k][s];
    }

    int i;
    int ans = INF;
    for(i=1; i<=n; i++)
    {
        if(get(s, i) < 2 && dict[k][i] != -1)
        {
            ans = min(ans, dfs(i, s + work(3, i-1)) + dict[k][i]);
        }
    }

    return dp[k][s] = ans;
}

int main()
{
    int i, j, k;
    while(cin >> n >> m)
    {
        memset(dict, -1, sizeof(dict));
        for(i=1; i<=m; i++)
        {
            int x, y, z;
            cin >> x >> y >> z;
            if(dict[x][y] == -1 || dict[x][y] > z)
            {
                dict[x][y] = z;
                dict[y][x] = z;
            }

        }
        for(i=1; i<=n; i++){
            dict[0][i] = 0;
        }

        memset(dp, -1, sizeof(dp));
        int ans = dfs(0, 0);
        if(ans == INF){
            cout << -1 << endl;
        }
        else{
            cout << ans << endl;
        }

    }

    return 0;
 } 

Islands and Bridges

POJ2288 题目链接

【题目大意】
哈密顿通路访问n个节点,每一种走法对应一个价值,如下计算The value of a Hamilton path C1C2…Cn is calculated as the sum of three parts. Let Vi be the value for the island Ci. As the first part, we sum over all the Vi values for each island in the path. For the second part, for each edge CiCi+1 in the path, we add the product Vi*Vi+1. And for the third part, whenever three consecutive islands CiCi+1Ci+2 in the path forms a triangle in the map, i.e. there is a bridge between Ci and Ci+2, we add the product Vi*Vi+1*Vi+2.
求最大价值和最大价值的路径种数。

【题目思路】
定义dp[i][j][k] 表示目前j节点上一个节点是i节点 处于k状态时到目标的最大价值
sum[i][j][k] 表示对应的取得最大价值的种数 。

【程序代码】

#include<iostream>
#include<cstring>
using namespace std;
long long dp[14][14][1<<13];    //dp[i][j][k] 表示目前j节点上一个节点是i节点 处于k状态时到目标的最大价值 
long long sum[14][14][1<<13];   //取得最大价值的种数 
int dict[14][14];
int n, m;
int array[14];
const long long FINF = -999999999999;

long long dfs(int v, int t, int s)
{
    if(s == (1<<n)-1)
    {
        sum[v][t][s] = 1;
        return 0;
    }
    if(dp[v][t][s] != -1){
        return dp[v][t][s];
    }

    int i;
    long long ans = FINF;
    for(i=1; i<=n; i++)
    {
        if((s&(1<<i-1)) == 0 && dict[t][i])
        {
            long long cur = dfs(t, i, s|(1<<i-1));
            if(dict[v][i]){
                cur += array[v] * array[t] * array[i];
            }

            if(ans == cur + array[t] * array[i])
            {
                sum[v][t][s] += sum[t][i][s|(1<<i-1)];
            }
            if(ans < cur + array[t] * array[i])
            {
                ans = cur + array[t] * array[i];
                sum[v][t][s] = sum[t][i][s|(1<<i-1)];
            }
        }
    }

    return dp[v][t][s] = ans;
}

int main()
{
    freopen("tt", "r", stdin);
    freopen("a", "w", stdout);
    int t;
    int i, j, k;
    cin >> t;
    while(t--)
    {
        cin >> n >> m;
        memset(dp, -1, sizeof(dp));
        memset(dict, 0, sizeof(dict));
        memset(sum, 0, sizeof(sum));

        int tot = 0;
        for(i=1; i<=n; i++)
        {
            cin >> array[i];
            tot += array[i];
        }

        for(i=1; i<=m; i++)
        {
            int x, y;
            cin >> x >> y;
            dict[x][y] = 1;
            dict[y][x] = 1;
        }
        for(i=1; i<=n; i++)
        {
            dict[0][i] = 1;
        }

        if(n==1){
            cout << tot << " " << 1 << endl;
            continue;
        }

        long long ans = dfs(0, 0, 0);
        if(sum[0][0][0] == 0){
            cout << 0 << " " << 0 << endl;
        }
        else{
            cout << ans+tot << " " << sum[0][0][0]/2 << endl;
        }

    }

    return 0;
 } 

猜你喜欢

转载自blog.csdn.net/qq_27008079/article/details/61635423
今日推荐