狀態壓縮dp入門題目總結——炮兵陣地和TSP問題

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;
 } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章