【hiho一下_week254】寻找最大值(高维前缀和)


原题

https://hihocoder.com/contest/hiho253

题目1 : 寻找最大值 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定N个数A1, A2, A3,
… AN,小Ho想从中找到两个数Ai和Aj(i ≠ j)使得乘积Ai × Aj × (Ai AND Aj)最大。其中AND是按位与操作。

小Ho当然知道怎么做。现在他想把这个问题交给你。

输入 第一行一个数T,表示数据组数。(1 <= T <= 10)

对于每一组数据:

第一行一个整数N(1<=N<=100,000)

第二行N个整数A1, A2, A3, … AN (0 <= Ai <2^20)

输出 一个数表示答案

样例输入
2
3
1 2 3
4
1 2 4 5
样例输出
12
80


我的思考

看到按位与,感觉应该是跟位数有关的,又0 <= Ai <2^20,感觉是在提示20位;
但是,真的没有想明白该怎么做。。。。。。唯一想到的还是暴力搜索

int main(int argc, char** argv) {
	int t, n;
	scanf("%d", &t);
	int A[100000];
	memset(A, 0, sizeof(A));
	long long int ans;
	while(t--) {
		ans = 0;
		scanf("%d", &n);
		for (int i = 0; i < n; i++) {
			scanf("%d", &A[i]);
		}
		for (int i = 0; i < n-1; i++) {
			for (int j = i+1; j < n; j++) {
				ans = (A[i]*A[j]*(A[i]&A[j]))>ans?(A[i]*A[j]*(A[i]&A[j])):ans;
			}
		}
		printf("%lld\n", ans);
	} 
	return 0;
}

但是,这样子是WA,不知道是为什么。。还以为会是TLE的。。。。


解析

如果我们用f[i][0/1]表示{Aj}中的最大值和次大值,其中Aj满足Aj & i = i

那么用如下代码可完成f数组的计算

for(int j = 19; j >= 0; j--) {
    for(int i = (1<<20) - 1; i >= 0; i--) {
        if(i & (1<<j)) {
            update(i - (1<<j), f[i][0]);
            update(i - (1<<j), f[i][1]);
        }
    }
}

其中f[i][0/1]的初始值是{Aj}中的最大值和次大值,其中Aj满足Aj = i

update(i, z) 表示用z更新f[i]

最终的答案ans = max(f[i][0] * f[i][1] * i)

上面这个计算技巧也被称为"n维前缀和"或者"高维前缀和"。大家可以参考 这里这里 以及搜索相关资料了解详情。


看了解析之后的答案

是因为早上起来太困??这个解析也是把我给绕晕了。。居然没有懂是什么意思。。。
看了一下http://www.cnblogs.com/wmrv587/p/6671323.html:

尝试枚举and值z,那么问题就变成了找寻最大的x*y,使得x&y==z

把这个要求放宽一点,我们来寻找z是x&y子集的情况(这样肯定不会丢掉整体最优解)

这意味着z是x的子集,且z是y的子集

问题就变成求包含z的集合中,最大的数字和次大的数字

这就是个高维前缀和维护问题了,dp解决

这个在讲解思路的时候相对就比较清楚了,开始看过解析之后的代码编写:

0513更新:
想了很久,感觉没什么必要像解析中那么写,只要借鉴思路(考虑&的值,找最大值和次大值)即可,于是最后改出的代码如下:

#include <iostream>
#include <memory.h>
#include <algorithm>
using namespace std;

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

const int N = (1<<20)+1; 

int main(int argc, char** argv) {
	int t, n;
	scanf("%d", &t);
	int f[N][2];
	
	while(t--) {
		memset(f, 0, sizeof(f)); 
		scanf("%d", &n);
		for (int i = 0; i < n; i++) {
			int temp;
			scanf("%d", &temp);
			// 更新最大值和次大值
			for (int j = temp; j > 0; j=(j-1)&temp) {
				if (temp > f[j][0]) {
					f[j][1] = f[j][0];
					f[j][0] = temp;
				}
				else if (temp > f[j][1]) {
					f[j][1] = temp; 
				} 
			} 
		}
		long long int ans = 0;
		for (int i = 0; i < N; i++) {
			ans = max(ans, 1ll*f[i][0]*f[i][1]*i);
		}
		printf("%lld\n", ans);
		// 
		
	} 
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值