HDU 3828 A+B problem

24 篇文章 0 订阅
2 篇文章 0 订阅
Problem Description
QQ and OO always play the game of A + B. QQ gives tow decimal number A and B, and OO answers the sum at once. But to do the same things too often is boring. So, today they are fed up with the easy game, and they come up with new rules of the game.
Rule1: When add A and B we use the binary system string of A and B.
Rule2: We can overlap the same suffix of A and prefix of B.
Rule3: If the binary system string of B is a substring of A we can use A to overlap B.

245       11110101

351               101011111

107             1101011

+-------------------------

 3935     111101011111

To make the problem more interesting, QQ gives n numbers, OO should use every one of them and every one once, then give the smallest of the sum. Now OO have no time to do it and need your help. You're a talented programmer you can do it.


Input
There are several tests, every test begin with n followed with n (0 < n < 16) lines, each line is a decimal number ai(0 < ai < 2^64) as described above.
Process until EOF.


Output
For each test you should print the smallest answer per line, maybe the answer is large you should mod 1000000009.


Sample Input
2
5
3
3
245
351
107


Sample Output
11

3935


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int n;
int lena[16];
char a[16][65];
int f[1 << 16][16]; // f[i][j] 状态i,以j为开头时,串最小长
int match[16][16];   //k = match[i][j], a[i][len - k..0] == a[j]

[len..k]

int DtoB(long long d, char *b){
	int i;
	for (i = 0; d > 0; i++){
		b[i] = d & 1;
		d = d >> 1;
	}
	return i;
}

int Cmp(int p, int q, int pre, int stat){
	int i, j;
	i = f[stat][p];
	j = f[stat][q];
	if (pre >= 0){
		i = i - match[pre][p];
		j = j - match[pre][q];
	}
	if (i > j) return 1;
	if (i < j) return -1;
	
	i = lena[p] - 1;
	j = lena[q] - 1;
	if (pre >= 0){
		i = i - match[pre][p];
		j = j - match[pre][q];
	}
	for (; i >= 0 && j >= 0; i--, j--)
		if (a[p][i] != a[q][j]) break;
	if (i < 0 || j < 0) return 0;
	if (a[p][i] > a[q][j]) return 1;
	else return -1;
}

void CalcMatch(){
	int i, j, p, q;
	for (i = 0; i < n; i++)
		for (j = 0; j < n; j++){
			if (i == j) continue;
			for (p = lena[i] - 1; p >= 0; p--){
				for (q = lena[j] - 1; q >= 0 && p - 

(lena[j] - 1 - q) >= 0; q--)
					if (a[i][p - (lena[j] - 1 - q)] 

!= a[j][q]) break;
				if (p - (lena[j] - 1 - q) < 0 || q < 0) 

break;
			}
			match[i][j] = lena[j] - q - 1;
		}
}

int main(){
	int i, j, k, i1, lent, pre, fstat;
	long long t;
	char ans[1024];
	
//	freopen("A+B.in","r",stdin);
//	freopen("out.txt","w",stdout);
	while(scanf("%d", &n) != EOF){
		fstat = (1 << n) - 1;
		for (i = 0; i < n; i++){
			scanf("%I64d", &t);
			lena[i] = DtoB(t, a[i]);
		}
		CalcMatch();
		memset(f[0], 0, sizeof(f[0]));
		
		for (i = 1; i <= fstat; i++){
			for (k = 0; k < n; k++){
				if (((1 << k) & i) == 0) continue;
				i1 = i - (1 << k);
				if (i1 == 0){
					f[i][k] = lena[k];
					continue;
				}
				f[i][k] = 0x7fffffff;
				for (j = 0; j < n; j++){
					if (((1 << j) & i1) == 0) 

continue;
					if (f[i][k] > f[i1][j] +  lena

[k] - match[k][j])
						f[i][k] = f[i1][j] + 

lena[k] - match[k][j];
				}
			}
		}

		pre = -1;
		lent = 0;
		while(fstat){
			k = -1;
			for (i = 0; i < n; i++){
				if (((1 << i) & fstat) == 0) continue;
				if (k < 0 || Cmp(i, k, pre, fstat) <= 

0)
					k = i;
			}

			fstat = fstat - (1 << k);
			if (pre >= 0) i = lena[k] - 1 - match[pre][k];
			else i = lena[k] - 1;
			if (i >= 0) pre = k;
			for (; i >= 0; i--, lent++)
				ans[lent] = a[k][i];
		}
		
		t = 0;
		for (i = 0; i < lent; i++){
			t = (t * 2 + ans[i]) % 1000000009;
		}
		printf("%I64d\n", t);
	}
	return 0;
}

/****************
一看到n<16,就想到TSP问题,集合DP
f[i][j] 状态i,以j为开头时,串最小长
先把最小串长求出来,之后再把串搜出来。
由于是以j为开头,所以当两个状态有相同长度时,取串j字典序小的
这样保证了得到结果最小。
之所以不直接记录串本身,只记录串长,是觉得貌似不满足最优子结构性质,即

原串字典序最小,子串字典序不一定也最小的感觉。。。其实也看怎么设计了。

。。反正这么写过一次WA了。。
注意,相交可以预处理出来,集合要反复用时,最好也先预处理出来
*****************/

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值