poj2288 状压dp

传送门
题意:有n个点,m条边,每个点有一个权值,求一条哈密顿回路,但是权值计算不同,包括三部分:1,经过的所有点的权值相加。2,经过的连续两个点的权值的乘积。3,能够构成三角型的连续三个点的乘积。这些全部加起来就是这条回路的总权值。输出最大权值和这个最大权值的路线有多少条。
状压dp,由于当前位置只和前两个点的位置有关,所以我们用dp[s][i][j],表示从i走到了j,目前倒数第二走到的是i,倒数第一走到的是j,目前走过的岛的状态集合是s(其二进制表示状态,0<=s<=(1<<13)-1,最多13个岛)的权值,ways[s][i][j]表示路径的条数。
首先把已知的边的权值算出来,

for(int i=0; i<n; i++)
        for(int j=0; j<n; j++)
        {
            if(Map[i][j])
            {
                 dp[(1<<i)|(1<<j)][i][j]=val[i]+val[j]+val[i]*val[j];
                 ways[(1<<i)|(1<<j)][i][j]=1;
            }

        }

很好理解,第一个地点走i岛,第二个地点走j岛,状态就是用’或’表示,权值就是val[i]+val[j]+val[i]*val[j],满足两个相连的点嘛,加上自身值再加上乘积。
然后我们就可以枚举了,我们倒数第一个点在j,倒数第二个点在i,那么如果有一条路径j->k,并且k还没走过呢(状态s里没有第k个岛,即s的二进制第k位为0),我们就从j走到k,下面分两种情况;
1,k与i点不可直达,dp[s|(1<<k)][j][k]=dp[s][i][j]+val[k]+val[k]*val[j]
2,k可以直达i,

dp[s|(1<<k)][j][k]=dp[s][i]j] + val[k] + val[j] * val[k] + val[i] * val[j] * val[k];

因为我们要求最大值,所以赋值之前比较一下,比之前大才更新,并同时更新ways[s|k][j][k],如果相等的话,我们就只更新ways,ways[s|(1<<k)][j][k]+=ways[s][i][j];
最后遍历一遍所有状态s为“n个1”(s=(1<<n)-1)的dp[s][i][j]值,选择最大的即可,

int s=(1<<n)-1;
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<n; j++)
            {
                if(Map[i][j])
                {
                    if(dp[s][i][j]>ans1)
                    {
                        ans1=dp[s][i][j];
                        ans2=ways[s][i][j];
                    }
                    else if(dp[s][i][j]==ans1)
                        ans2+=ways[s][i][j];
                }
            }
        }

ans1更新权值,ans2更新路径条数
AC代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N=13;
LL dp[(1<<N)][N][N],ways[(1<<N)][N][N];
LL val[N];
int Map[N][N];
void solve(int n)
{
    memset(dp,-1,sizeof(dp));
    memset(ways,0,sizeof(ways));
    for(int i=0; i<n; i++)
        for(int j=0; j<n; j++)
        {
            if(Map[i][j])
            {
                 dp[(1<<i)|(1<<j)][i][j]=val[i]+val[j]+val[i]*val[j];
                 ways[(1<<i)|(1<<j)][i][j]=1;
            }

        }

    for(int s=0; s<(1<<n); s++)
    {
        for(int i=0; i<n; i++)
            if(s&(1<<i))///s包含第i个岛
            {
                for(int j=0; j<n; j++)
                    if(s&(1<<j)&&Map[i][j]&&dp[s][i][j]!=-1)///s包含第j个岛,且dp不为-1,且i-j有路径
                    {
                        for(int k=0; k<n; k++) ///寻找j到k的路径
                            if(Map[j][k]&&!(s&(1<<k)))///s不包含k岛,但是j到k有路径,所以我们要走一走
                            {
                                LL temp = dp[s][i][j]+val[k]+val[k]*val[j];///单纯的走到k的权值保存一下
                                if(Map[k][i])///k能到i的话形成三角
                                {
                                    temp+=val[i]*val[j]*val[k];
                                }
                                if(dp[s|(1<<k)][j][k]<temp)
                                {
                                    dp[s|(1<<k)][j][k]=temp;
                                    ways[s|(1<<k)][j][k]=ways[s][i][j];
                                }
                                else if(dp[s|(1<<k)][j][k]==temp)
                                {
                                    ways[s|(1<<k)][j][k]+=ways[s][i][j];
                                }
                            }
                    }
            }
    }
}
int main()
{
    int T,n,m;
    int u,v;
    cin>>T;
    while(T--)
    {
        cin>>n>>m;
        memset(Map,0,sizeof(Map));
        for(int i=0; i<n; i++)
            cin>>val[i];

        for(int i=0; i<m; i++)
        {
            cin>>u>>v;
            u--;
            v--;
            Map[u][v]=Map[v][u]=1;
        }
        if(n==1)
        {
            printf("%lld 1\n",val[0]);
            continue;
        }
        solve(n);
        LL ans1,ans2;
        ans1=ans2=0;
        int s=(1<<n)-1;
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<n; j++)
            {
                if(Map[i][j])
                {
                    if(dp[s][i][j]>ans1)
                    {
                        ans1=dp[s][i][j];
                        ans2=ways[s][i][j];
                    }
                    else if(dp[s][i][j]==ans1)
                        ans2+=ways[s][i][j];
                }
            }
        }
        cout<<ans1<<" "<<ans2/2<<endl;
    }

    return 0;
}
/**



2
3 3
2 2 2
1 2
2 3
3 1
4 6
1 2 3 4
1 2
1 3
1 4
2 3
2 4
3 4

*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值