1. 程式人生 > >【狀壓dp】【POJ2288】Islands and Bridges【Hamilton路】

【狀壓dp】【POJ2288】Islands and Bridges【Hamilton路】

題意:

      給你一張圖,找到一條Hamilton路,要求從其中一個節點出發,遍歷圖中所有節點一遍。

      這樣一條路徑的權值計算為三部分的累加。

      第一部分:這條路徑上所有節點的權值之和。

      第二部分:這條路徑上相鄰兩點的乘積。

      第三部分:這條路徑上相鄰三個點的乘積。(如果三個點互相直接相連)

      如,a1、a2、a3、a4這四個點形成的一條路徑,第一部分為w1+w2+w3+w4,第二部分為w1*w2+w2*w3+w3*w4,第三部分為如果a1和a3直接相連,a2和a4直接相連,則為a1*a2*a3+a2*a3*a4。

      要求輸出按照上述計算方法所能找到的最大的Hamilton路,並且輸出這條路的個數,n <= 13。

 

思路:

      因為這樣一條路徑的更新涉及到最後兩個點,所以三維dp肯定要記錄最後兩個節點了,然後第一維用狀態壓縮記錄當前走過的節點,第二維記錄這條路徑最後一個節點,第三維記錄倒數第二個節點。

      dp[i][j][k]表示當前狀態為i,最後一個節點為j,倒數第二個節點為k。

 

      然後dp[i][j][k]去更新dp[i|(1<<p)][p][j],四個for迴圈結束本題。

      本題細節較多,比如num會爆int,可能會出現0 x這樣的答案等等,詳見程式碼。

 

總結:

      此類狀壓dp,大都較為相似,先開一維記錄狀態,再根據題意,看需要記錄一個數值,多記錄一個數值則需要多開一維狀態。

 

程式碼:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
typedef long long ll;

int n,m;
int a[20];
int w[20][20];
int dp[1<<13][15][15];
ll way[1<<13][15][15];

void solve()
{
    memset(dp,-1,sizeof dp);
    rep(i,0,n-1)
        rep(j,0,n-1)
            if(w[i][j])
                dp[(1<<i)|(1<<j)][i][j] = a[i]+a[j]+a[i]*a[j],
                way[(1<<i)|(1<<j)][i][j] = 1;
    rep(i,0,(1<<n)-1)
        rep(j,0,n-1)  //最後一個
            if(i&(1<<j))
                rep(k,0,n-1)  //倒數第二個
                    if((i&(1<<k)) && w[j][k] && dp[i][j][k] != -1){
                    //  cout << "$$$$" << endl;
                        rep(h,0,n-1)//倒數第三個
                            if(!(i&(1<<h)) && w[h][j])
                            {
                                int ans = dp[i][j][k]+a[j]*a[h]+a[h];
                                if(w[h][k]) ans += a[k]*a[h]*a[j];  
                                if(ans == dp[i^(1<<h)][h][j]) way[i^(1<<h)][h][j]+=way[i][j][k];
                                else if(ans > dp[i^(1<<h)][h][j]){
                                    dp[i^(1<<h)][h][j] = ans;
                                    way[i^(1<<h)][h][j] = way[i][j][k];
                                }                           
                            }
                    }
    int maxn = 0;
    ll num = 0;
    rep(i,0,n-1)
        rep(j,0,n-1)
            if(w[i][j]){
                if(maxn < dp[(1<<n)-1][i][j])
                    maxn = dp[(1<<n)-1][i][j], num = way[(1<<n)-1][i][j];
                else if(maxn == dp[(1<<n)-1][i][j]) num += way[(1<<n)-1][i][j];
            }
    printf("%d %lld\n",maxn,num/2);
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(w,0,sizeof w);
        memset(way,0,sizeof way);
        scanf("%d%d",&n,&m);
        rep(i,0,n-1) scanf("%d",&a[i]);
        if(n == 1) {
            printf("%d 1\n", a[0]);
            continue ;
        }
        rep(i,1,m)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            x--, y--;
            w[x][y] = 1, w[y][x] = 1;
        }
        solve();
    }
    return 0;
}

/*
dp[1<<13][15][15]:last next
*/