下面分析引用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;
}