HDU 5691

题意:度度熊是他同时代中最伟大的数学家,一切数字都要听命于他。现在,又到了度度熊和他的数字仆人们玩排排坐游戏的时候了。游戏的规则十分简单,参与游戏的N个整数将会做成一排,他们将通过不断交换自己的位置,最终达到所有相邻两数乘积的和最大的目的,参与游戏的数字有整数也有负数。度度熊为了在他的数字仆人面前展现他的权威,他规定某些数字只能在坐固定的位置上,没有被度度熊限制的数字则可以自由地交换位置
输出数字重新排列后最大的所有相邻两数乘积的和,即max{a1⋅a2+a2⋅a3+......+aN−1⋅aN}


题解:一道状压DP的题目。其实关于状压DP的题目很久之前便有所接触,只是一直没去学,今天通过此题算是初步学习了状压DP。

状压DP本质上来说还是DP,重点在于找出每一步的状态,并且建立起走到下一步的状态转移方程。不过有的时候,状态的表示会要利用到状态压缩的方法。

 状态转移方程:当第k个位置已经被固定填什么数时,向状态st里添加i并以i为结尾,dp[st|(1<<q[k])][q[k]] = max(dp[st|(1<<q[k])][q[k]], dp[st][j]+a[j]*a[q[k]])。当第k位置不固定,枚举各个数,该数不在集合时,便添加到集合里,dp[st|(1<<i)][i] = max(dp[st|(1<<i)][i], dp[st][j]+a[j]*a[i])。

代码如下:

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
#define ll long long
using namespace std;
const int maxn=1<<17;
const ll inf=1e14;
int n,num[20],fix[20],pos[20];
ll dp[maxn][20];
bool ok(int p,int s){
    while(s){
        s=s&(s-1);
        p--;
    }
    if(p) return false;
    return true;
}
int main(){
    int T;
    scanf("%d",&T);
    int Case=1;
    while(T--){
        scanf("%d",&n);
        int a,p;
        memset(fix,0,sizeof(fix));
        memset(pos,0,sizeof(pos));
        for(int i=0;i<n;++i){
            scanf("%d%d",&a,&p);
            num[i+1]=a;
            if(++p) pos[p]=i+1,fix[i+1]=1;
        }
        for(int i=0;i<=(1<<(n));++i)
            for(int j=0;j<=n;++j)
            dp[i][j]=-inf;
        if(pos[1]) dp[1<<(pos[1]-1)][pos[1]]=0;
        else {
            for(int i=1;i<=n;++i)
                if(fix[i]==0)
                dp[1<<(i-1)][i]=0;
        }
        for(int p=2;p<=n;++p){
            for(int s=0;s<(1<<n);++s){
                if(ok(p-1,s)){
                    if(pos[p]){
                        for(int l=1;l<=n;++l){
                            if(dp[s|(1<<(pos[p]-1))][pos[p]] < dp[s][l]+num[l]*num[pos[p]]&& dp[s][l]!=-inf){
                                dp[s|(1<<(pos[p]-1))][pos[p]]=dp[s][l]+num[l]*num[pos[p]];
                            }
                        }
                    }
                    else{
                        for(int l1=1;l1<=n;++l1){
                            if(fix[l1]==0&&(s&(1<<(l1-1)))==0){
                                for(int l2=1;l2<=n;++l2){
                                    if(dp[s|(1<<(l1-1))][l1] < dp[s][l2]+num[l2]*num[l1]&& dp[s][l2]!=-inf)
                                        dp[s|(1<<(l1-1))][l1] = dp[s][l2]+num[l2]*num[l1];
                                }
                            }
                        }
                    }
                }
            }
        }


        ll ans=-inf;
        for(int i=1;i<=n;++i)
            if(dp[(1<<n)-1][i]>ans)
            ans=dp[(1<<n)-1][i];
        printf("Case #%d:\n",Case++);
        if(n==1) printf("%d\n",a);
        else printf("%I64d\n",ans);
    }
    return 0;
}

等我费劲心脾写完上述代码,百度了下别人题解,便发现了自己年轻。由于没有很好掌握状态转移,导致以上代码很渣,下面贴一份别人的正解代码。

正解代码:

#include <cstdio>  
#include <cstring>  
#include <algorithm>  
using namespace std;  
  
typedef long long ll;  
const int N = 20;  
const ll INF = 1LL<<50;  
ll dp[1<<N][N];  
int p[N], q[N], a[N];  
  
int main() {  
    int T, n;  
    scanf("%d", &T);  
    for(int kase = 1; kase <= T; kase++) {  
        scanf("%d", &n);  
  
        memset(q, -1, sizeof(q));  
        for(int i = 0; i < n; i++) {  
            scanf("%d%d", &a[i], &p[i]);  
            if(p[i] >= 0) q[p[i]] = i;  
        }  
        for(int i = 0; i < (1<<n); i++)  
            for(int j = 0; j < n; j++)  
                dp[i][j] = -INF;  
        if(~q[0]) dp[1<<q[0]][q[0]] = 0;  
        else {  
            for(int i = 0; i < n; i++)  
                dp[1<<i][i] = 0;  
        }  
        for(int st = 1; st < (1<<n); st++) {  
            int k = 0;  
            for(int i = 0; i < n; i++)  
                k += st>>i&1;  
            if(~q[k]) {  
                if(st & (1<<q[k])) continue;  
                for(int j = 0; j < n; j++) {  
                    if(~st&(1<<j)) continue;  
                    dp[st|(1<<q[k])][q[k]] = max(dp[st|(1<<q[k])][q[k]], dp[st][j]+a[j]*a[q[k]]);  
                }  
            }  
            else {  
                for(int i = 0; i < n; i++) {  
                    if(st & (1<<i)) continue;  
                    for(int j = 0; j < n; j++) {  
                        if(~st&(1<<j)) continue;  
                        dp[st|(1<<i)][i] = max(dp[st|(1<<i)][i], dp[st][j]+a[j]*a[i]);  
                    }  
                }  
            }  
        }  
        ll ans = -INF;  
        for(int i = 0; i < n; i++)  
            ans = max(ans, dp[(1<<n)-1][i]);  
        printf("Case #%d:\n%I64d\n", kase, ans);  
    }  
    return 0;  
}  


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值