POJ 2288 状压DP

题意

寻找哈密顿图。路径上的每个点都有权值,对于每条边,权值为v[i]*v[j]。如果i和j相连,j和k相连,并且i和k之间有桥,则权值再加上v[i]*v[j]*v[k]。问权值最大的路径权值是多少?有多少种组成该权值路径的方案,一个方案的正序和逆序视为一个方案。

题解

自己处理的时候考虑的不严密,做了很多麻烦的东西。比如说对于是否存在通路的判断,自己考虑的时候选用了并查集去进行处理,但实际上这是完全没有必要的,只需要在dp数组赋初始值的时候谨慎一点(注意过滤掉无通路的情况)就可以了。
还有就是对于上一个节点的记录,自己在处理的时候专门开了一个数组进行记录,搞得很麻烦。看了Kuangbin大佬的题解才意识到,dp数组多开一维,就可以很方便的记录上一个节点。ORZ。
另外的话,关于路径的统计,一直没有找到好的办法,因为正序和逆序路径判断很难搞,我自己记录了一下起始点和终止点,但这样并不能很好的去重。最后依然是参考了Kuangbin大佬的题解,在DP数组状态转移的时候搞一个num数组,用来专门记录可以转移到这个状态的路径数目。最后由于统计了正向和逆向的,正好是去重后的二倍,因此/2就可以了。

代码

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 8200
#define EPS 1e-10
#define MOD 100000000

using namespace std;

int dp[15][15][MAXN];
int num[15][15][MAXN];
bool can[15][15];
int value[15];

int main() {
    int kase;
    scanf("%d",&kase);
    W(kase--) {
        memset(can,false,sizeof(can));
        memset(dp,-1,sizeof(dp));
        memset(num,0,sizeof(num));
        int n,m;
        scanf("%d%d",&n,&m);
        UP(i,0,n) {
            scanf("%d",&value[i]);
        }
        if(n==1) {
            printf("%d 1\n",value[0]);
            continue;
        }
        UP(i,0,m) {
            int a,b;
            scanf("%d%d",&a,&b);
            a--,b--;
            can[a][b]=can[b][a]=true;
        }
        UP(i,0,n) {
            UP(j,0,n) {
                if(i==j||!can[i][j])
                    continue;
                dp[i][j][(1<<i)|(1<<j)]=value[i]*value[j];
                num[i][j][(1<<i)|(1<<j)]=1;
            }
        }
        UP(s,0,1<<n) {
            UP(i,0,n) {
                if(!(s&(1<<i)))
                    continue;
                UP(j,0,n) {
                    if(i==j||!(s&(1<<j))||!can[i][j])
                        continue;
                    UP(k,0,n) {
                        if(i==k||j==k||!(s&(1<<k))||!can[j][k]||dp[j][k][s-(1<<i)]==-1)
                            continue;
                        int add=value[i]*value[j];
                        if(can[i][k]) {
                            add+=value[k]*value[i]*value[j];
                        }
                        if((dp[j][k][s-(1<<i)]+add)>dp[i][j][s]) {
//                            cout<<j<<" "<<k<<" "<<s-(1<<i)<<" "<<add<<" "<<dp[j][k][s-(1<<i)]<<endl;
                            dp[i][j][s]=dp[j][k][s-(1<<i)]+add;
                            num[i][j][s]=num[j][k][s-(1<<i)];
                        } else if((dp[j][k][s-(1<<i)]+add)==dp[i][j][s]) {
                            num[i][j][s]+=num[j][k][s-(1<<i)];
                        }
                    }
                }
            }
        }
        int maxsum=0;
        LL maxnum=0;
        UP(i,0,n) {
            UP(j,0,n) {
                if(i==j||!can[i][j])
                    continue;
                if(dp[i][j][(1<<n)-1]>maxsum) {
                    maxsum=dp[i][j][(1<<n)-1];
                    maxnum=num[i][j][(1<<n)-1];
                } else if(dp[i][j][(1<<n)-1]==maxsum) {
                    maxnum+=num[i][j][(1<<n)-1];
                }
            }
        }
        if(maxsum!=0) {
            UP(i,0,n) {
                maxsum+=value[i];
            }
        }
        printf("%d %I64d\n",maxsum,maxnum/2);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值