HDU 5691(状态压缩dp)详解

下面分析引用ac_dao_di博客内容/*
hdu5691
题意:给定n个数,及其对应的位置,位置为-1,表示位置任意,否则其位置是固定的。
1 <= n <= 16
求max(a1 * a2 + a2 * a3 + a3 * a4 + … + an-1 * an)
分析:
使用状态dp,旅行商问题的变型。
首先对数字按输入的顺序编号,第一个输入的编号0,第二个输入的编号1,…
对于状态i,用二进制形式表示(从低位到高位依次编号,与上述编号的数字一一对应)。
如果该位是1,则表示该编号的数字被选择。假设该状态中1的个数有x个,这x个数与排列队伍中的前x个一一对应。
1. 如果第(x - 1)个位置,也就是最后一个位置,已经被占据,那么当前只能以占据该位置的数字编号结尾。
不以该编号结尾的状态都是非法的,即j不等于该编号,dp[i][j]非法,可能当前状态i的编号位不为1,即i & (1 << 编号) == 0
此时所有状态都非法。
dp[i][j] 表示将编号j放到x - 1位。
2. 如果第(x - 1)个位置可以填任意编号,那么该位也只能填那些没有固定位置的编号,有固定位置的编号是非法的,
dp[i][j],如果编号j被占用(在其他位置,不能用于第(x - 1)位),非法。
对于合法的状态i,以j作为第(x - 1)个位置的编号,那么另一种状态i - (1 << j),同样以一种合法的编号k结尾,
则dp[i][j] = max(dp[i - (1 << j)][k] + a[k] * a[j],所有合法的结尾编号k);
3. 特别注意只有一个1的情况,此时状态合法则置0,作为初始化。
说一下为什么要多一维,表示该状态以哪编号结尾:
(1) 事实上,我们完全可以
dp[i] = max(dp[i], dp[i - (1 << j)] + a[k] * a[j],其中k(select[i - (1 << j)])
为dp[i - (1 << j)]取得最大值时对应的结尾编号,枚举所有合法j)
此时状态i有对应的最优选择select[i]
这时候k直接确定,是唯一的,不需要另外枚举。这种做法忽视了动态规划的特点:无后效性,后一项a[j]显然依赖于a[k]。
虽然我们保证了dp[i - (1 << j)]是以所有合法编号结尾中值最大的,但是却不能保证这个k是合适的,例如
这时候a[k]是所有合法结尾中数值最小的,但a[j]却非常大,显然如果a[k]能够大一点,则a[k] * a[j]占主要比重,
将使得dp[i]的值越大。当然,这种缺陷可能可以通过枚举j来弥补。
(2) 为了方便找到a[k],同时也枚举了(i - (1 << j))的所有可能的结尾,来计算a[k] * a[j],这种计算是相当全面的。
(3) 相当于一条相连接的路径,一步一步走。
举例:
1
4
10 1
5 -1
20 -1
15 2
编号10 –> 0 5 –> 1 20 –> 2 15 –> 3
vis[0] = 1, vis[3] = 1,即编号0和编号3都已经固定,不能随意使用到其他位置。
显然pos[1] = 0, pos[2] = 3,即位置1被编号0占据,位置2被编号3占据

对所有二进制状态求值:
1. 把编号0放到位置0
dp[1][0] = 非法,因为编号0已经有主...

2. 把编号1放到位置0,合法,因为编号1无主,且位置0没被占据。
dp[10][1] = 0

3. 把编号0和1放到位置0和1
    3.1 把编号0放到位置1
        dp[11][0] = dp[10][1] + a[1] * a[0] = 50
    3.2 把编号1放到位置1
        dp[11][1] = dp[01][0] + a[0] * a[1],非法,因为编号0不能放到位置0
4. 把编号2放到位置0,合法
dp[100][2] = 0
5. 把编号0和2放到位置0和1
    5.1 把编号0放到位置1,合法,因为编号0固定在位置1
        dp[101][0] = dp[100][2] + a[2] * a[0] = 200
    5.2 把编号2放到位置1,不合法,因为位置1已经有了编号0
        dp[101][2] = dp[001][0] + a[0] * a[2] 非法
6. 把编号1和2放到位置0和1
    6.1 把编号1放到位置1,非法,因为位置1已经有编号0
        dp[110][1] = dp[100][2] + a[2] * a[1]非法
    6.2 把编号2放到位置1,非法,因为位置1已经有编号0
        dp[110][2] = dp[010][1] + a[1] * a[2]非法
7. 把编号0,1,2放到位置0, 1, 2
    7.1 把编号0放到位置2,非法,编号0已经有主
        dp[111][0] = dp[110][1] + a[1] * a[0] 
                 dp[110][2] + a[2] * a[0]
    7.2 把编号1放到位置2,非法,因为位置2已经有编号3
        dp[111][1]
    7.3 把编号2放到位置2,非法,因为位置2已经有编号3
        dp[111][2] 
8. 把编号3放到位置0
    dp[1000][3]非法,因为编号3已经有主
9. 把编号0和3放到位置位置0和1
    9.1 把编号0放到位置1,合法,编号0固定在位置1
        dp[1001][1] = dp[1000][3] + a[3] * a[1]非法
    9.2 把编号3放到位置1,非法,编号3已经固定在位置2
        dp[1001][3] = dp[0001][0] + a[0] * a[3]非法
10. 把编号1和3放到位置0和1
    10.1 把编号1放到位置1,非法,位置1已经有编号0
        dp[1010][1] = dp[1000][3] + a[3] * a[1]非法
    10.2 把编号3放到位置1,非法,位置1已经有编号0
        dp[1010][3] = dp[0010][1] + a[1] * a[3]非法
11. 把编号0,1,3放到位置0,1,2
    10.1 把编号0放到位置2,位置2已经有编号3了
        dp[1011][0] = dp[1010][1]
                  dp[1010][3]都非法
    10.2 把编号1放到位置2,位置2已经有编号3了
        dp[1011][1] = dp[1001][0]
                  dp[1001][3]都非法
    10.3 把编号3放到位置2,合法
        dp[1011][3] = dp[0011][0] +  a[0] * a[3] = 50 + 10 * 15 = 200 (位置0,1,2依次为5, 10, 15)
                  dp[0011][1] 非法
12. 把编号2和3放到位置0和1
    12.1 把编号2放到位置1,非法,位置1已经有编号0
        dp[1100][2] = dp[1000][3] + a[3] * a[2]
    12.2 把编号3放到位置1,非法,位置1已经有编号0
        dp[1100][3] = dp[0100][2] + a[2] * a[3]
13. 把编号0,2,3放到位置0,1,2
    13.1 把编号0放到位置2,非法,编号0已经固定在位置1,且位置2只能放置编号3
        dp[1101][0]
    13.2 把编号2放到位置2,非法,位置2只能放置编号3
        dp[1101][2]
    13.3 把编号3放到位置2,合法
        dp[1101][3] = dp[0101][0] + a[0] * a[3] = 200 + 10 * 15 = 350(位置0,1,2依次为20, 10, 15)
                  dp[0101][2]非法
14. 把编号1,2,3放到位置0,1,2
    14.1 把编号1放到位置2,非法,位置2只能放置编号3
        dp[1110][1]
    14.2 把编号2放到位置2,非法,位置2只能放置编号3
        dp[1110][2]
    14.3 把编号3放到位置2,合法
        dp[1110][3] = dp[0110][1]
                  dp[0110][2]都非法
15. 把编号0,1,2,3放到位置0,1,2,3
    15.1 把编号0放到位置3,非法,编号0固定在位置1
        dp[1111][0]
    15.2 把编号1放到位置3,合法
        dp[1111][1] = dp[1101][0]非法
                  dp[1101][2]非法
                  dp[1101][3] + a[3] * a[1] = 350 + 15 * 5 = 425(位置0,1,2依次为20, 10, 15, 5)
    15.3 把编号2放到位置3,合法
        dp[1111][2] = dp[1011][0]非法
                  dp[1011][1]非法
                  dp[1011][3] + a[3] * a[2] = 200 + 15 * 20 = 500 (位置0,1,2依次为5, 10, 15, 20)
    15.4 把编号3放到位置3,非法,编号3固定在位置2
                  dp[1111][3]
最后的结果为max(dp[1111][0~3]) = 500

*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int INF=0x3f3f3f3f;

int a[20],p[20];
int dp[(1<<20)][20];
int main()
{
    int T;
    int i,j,k; 
    int n,st,Max,cas;
    scanf("%d",&T);
    for(cas=1;cas<=T;cas++)
    {                    //dp[i][j]表示状态是i时,最后一个取j时的最大值
        scanf("%d",&n);
        st=-1;
        for(i=0;i<n;i++)
        {
          scanf("%d%d",&a[i],&p[i]);
        }
        for(i=0;i<(1<<n);i++)
        {
          for(j=0;j<n;j++)
          {
            dp[i][j]=-INF;//因为求最大值而且有负数,所以不能初始化为0要初始化为负的无限大
          }
        }
        for(i=0;i<n;i++)
        {
            if(p[i]==0||p[i]==-1)
            { 
                dp[(1<<i)][i]=0;  //下文通过判断是不是-INF来决定这个数能不能改变位置,所以能移动的数包括,第一个数,因为第一个数,
            }    
        }     
        for(i=0;i<(1<<n);i++)
        {
            for(j=0;j<n;j++)
            {
                if(dp[i][j]!=-INF)//不是-INF进入if
                for(k=0;k<n;k++)
                {             
                    if(((1<<k)&i)==0&&(p[k]==__builtin_popcount(i)||p[k]==-1))
                    //1.判断该位置在该状态是否
                    //2.如果该位置正好就是p[k]位不管他能否移动都可以插入
                    //3.如果为-1一定可以移动
                    dp[i|(1<<k)][k]=max(dp[i|(1<<k)][k],dp[i][j]+a[j]*a[k]);
                }
            }
        }
        Max=-INF;
        for(i=0;i<n;i++)
        {
         Max=max(Max,dp[(1<<n)-1][i]);
        }

        printf("Case #%d:\n%d\n",cas,Max);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值