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

下面例子对应的图片:

/*
	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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值