您的位置:首页 > 其它

hdu 5691 Sitting in Line(百度之星A轮) 状压_旅行商问题变型

2016-05-22 23:06 253 查看
下面例子对应的图片:



/*
2016.5.22 22:43
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 <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 16 + 1;
int dp[1 << MAXN][MAXN], pos[MAXN], a[MAXN], b[MAXN], vis[MAXN];
//计算data中二进制1的个数
int getBits(int data){
int res = 0;
while(data){
res += (data & 1);
data >>= 1;
}
return res;
}
int main(){
int t, n, i, up, j, k, cnt, ans, cas = 1;
scanf("%d", &t);
while(t--){
scanf("%d", &n);
fill(pos, pos + n, INT_MIN);
memset(vis, 0, sizeof(vis));
for(i = 0; i < n; i++){
scanf("%d%d", &a[i], &b[i]);
if(b[i] >= 0){
pos[b[i]] = i;//位置b[i]已经被编号i占据
vis[i] = 1;//编号i不能随意使用
}
}
up = 1 << n;
for(i = 1; i < up; i++){
cnt = getBits(i);//获取二进制状态i中1的个数
for(j = 0; j < n; j++){
if(!(i & (1 << j))){
continue;
}
dp[i][j] = INT_MIN;//不满足条件者,或者没被下面更新者,都是不合法状态
if(cnt == 1){
//位置0没被占据,且编号j没被使用,或者位置0固定放置编号j,则编号j可以放到位置0
if(pos[0] == INT_MIN && !vis[j] || pos[0] == j){
dp[i][j] = 0;
}
//printf("i = %d, j = %d, dp = %d \n", i, j, dp[i][j]);
break;
}
//位置cnt - 1没被占据,且编号j没被使用,或者位置cnt - 1固定放置编号j,则编号j可以放到位置cnt - 1
if(pos[cnt - 1] == INT_MIN && !vis[j] || j == pos[cnt - 1]){
for(k = 0; k < n; k++){
//枚举i - (1 << j)中的二进制1(编号),作为结尾,不能与j相同
//且该状态必须合法
if(k == j || !((1 << k) & i) || dp[i - (1 << j)][k] == INT_MIN){
continue;
}
dp[i][j] = max(dp[i][j], dp[i - (1 << j)][k] + a[k] * a[j]);
}
}
//printf("i=%d, j = %d, dp = %d \n", i, j, dp[i][j]);
}
//puts("");
}
//求取最终所有位置排满时的最值。
ans = INT_MIN;
for(i = 0; i < n; i++){
ans = max(ans, dp[up - 1][i]);
}
printf("Case #%d:\n%d\n", cas++, ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  dp 状压