CF578B 贪心+预处理优化+思维到位

题目描述:

D. "Or" Game
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

You are given n numbers a1, a2, ..., an. You can perform at most k operations. For each operation you can multiply one of the numbers by x. We want to make  as large as possible, where  denotes the bitwise OR.

Find the maximum possible value of  after performing at most k operations optimally.

Input

The first line contains three integers nk and x (1 ≤ n ≤ 200 0001 ≤ k ≤ 102 ≤ x ≤ 8).

The second line contains n integers a1, a2, ..., an (0 ≤ ai ≤ 109).

Output

Output the maximum value of a bitwise OR of sequence elements after performing operations.

Sample test(s)
input
3 1 2
1 1 1
output
3
input
4 2 3
1 2 4 8
output
79
Note

For the first sample, any possible choice of doing one operation will result the same three numbers 112 so the result is .

For the second sample if we multiply 8 by 3 two times we'll get 72. In this case the numbers will become 12472 so the OR value will be 79 and is the largest possible result.

题目链接:codeforces 578B

题目大概意思:
给定n个数字,a1,a2,a3....an(数据范围是0到10^9),然后再给定两个数字,一个数字是k,另外一个是x。接下来你可以对这n个数进行k次操作,每次操作对其中任意一个数乘以x,最后得到操作后的数组,再对这n个数进行按位或运算,题目要求的是对这n个数进行操作之后,进行按位或运算能得到的最大的数

做题的思路:首先我们知道,按位或运算的运算规则是:

1|1=1
1|0=1
0|1=1
0|0=0
因此,我们很容易想到,让二进制位最多的那个数去乘x就好了,这样的话最后n个数按位或运算后的二进制位也是最多的,当二进制位最多的数只有一个的话,只要将这个数乘以k次x就好了,但是,如果 二进制位最多的数不止一个的话,那该哪个数乘以x才能使最后的结果最大呢?其实很容易想到,我们直接遍历一遍就好了,只要找到其中的一个数乘以x,然后再进行下一次操作,巧妙之处来了,那总共的操作次数有k次,这样的话该怎么操作呢?其实这个问题也很简单,由于x是大于等于2的,又因为我们第一次操作是在二进制位最多的数中选一个进行*x操作,那么这个数在这次操作之后,所有二进制位至少是左移一位的(即二进制位数比原来多1位)。那么在后来的操作进行时,这个数已经是所有数中二进制位数最多的数了,理所当然还是操作这个数,往后的话以此类推,所以从这里我们可以得出,k次操作都是在一个数上进行的,这样的话,题目难度便下降了。

解决办法:这里主要是用到了贪心的思想,然后在计算sum[i] = a[0] | a[1] | a[2] .... a[i-1] | a[i+1] | a[i+2] .... a[n-1]时,用到了优化的思想,主要是用到了预处理优化

预处理优化之——前缀和优化:

非常常见的优化方式。此处的前缀和指某数组的前i项和。
如给定数组a[n],求sum[n]。其中sum[i]=a[0]+a[1]+...+a[i]
这里的sum[]数组即为数组a的前缀和数组。
那么前缀和有什么用处呢?
假设我要求a[x]+a[x+1]+a[x+2]+...+a[y]
那么这个答案就等于 sum[y]-sum[x-1](x!=0)。若x=0则等于sum[y]。
有一点:如果原数组的数字是非负的,那么sum数组则是非递减的,在搜索的时候可以考虑使用二分。

其他预处理优化。
预处理根据题目需要求的问题,事先处理好保存起来,解决问题的时候可能需要多次使用,这样的话,只需要计算一遍,不用每次需要用的时候重复计算。

一开始的时候,我是这样求解的:

for(int j = 0;j < n;j++)
     for(int i = 0;i < n;i++)
      {
           if(i == j)
           {
                sum[j] = sum[j] | (a[j]*ans);
                 continue;
            }
            else sum[j] = sum[j] | a[i];
       }



这样求解之后,很快,评测出来了
 
 

答案超时了,我又读了一遍题目,发现这道题的数据量很大(200000),O(n^2)的时间复杂度肯定超时,于是我开始观察sum[i]数组的组成特点,如下所示:
sum[0] = (  ) | a[1] | a[2] | a[3] ..... | a[n-2] | a[n-1];
sum[1] = a[0]| (   ) | a[2] | a[3] ..... | a[n-2] | a[n-1];
sum[2] = a[0]| a[1] | (   ) | a[3] ..... | a[n-2] | a[n-1];
.
.
.
sum[n-2] = a[0] | a[1] | a[2] | ..... | a[n-3] | (   ) | a[n-1];
sum[n-1] = a[0] | a[1] | a[2] | ..... | a[n-3] | a[n-2] | (   );
(   ) 括号表示括号中的数是待操作的数字
所以呢,我就发现,在待操作的数字的两边(前缀和后缀)依次成正金字塔和倒金字塔形
sum[i+1]比sum[i]的前缀多了一个 | a[i]  而后缀少了一个 | a[i+1]
由于按位或运算时不可逆的,所以前缀可以从前往后依次加 |a[i],而后缀则从后往前
所以我就另外声明了两个数组pre[200005],suf[200005]
附上完整代码:
                            //时间复杂度O(n^2)超时,需要优化,已经优化成O(n)
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll a[200005],pre[200005],suf[200005];    //pre,suf分别存储标记数的前缀和后缀
int main()
{
    ll n,k,x;
    while(scanf("%I64d%I64d%I64d",&n,&k,&x)==3 && n && k && x)
    {
        ll ans = 1,tmp,sum = 0LL;
        for(int i = 0; i < k; i++)
        {
            ans = ans * x;        //用来记录操作数的总积
        }
        //printf("%I64d\n",ans);
        for(int i = 0; i < n; i++)
            scanf("%I64d",&a[i]);
        for(int i = 0; i < n-1; i++)
            pre[i + 1] =  pre[i] | a[i];  //标记数的前缀
        for(int i = n - 1;i > 0;i--)
            suf[i-1] = suf[i] | a[i];  //标记数的后缀
        for(int i = 0;i < n;i++)
        {
            tmp = pre[i] | (a[i]*ans) | suf[i];
            sum = max(tmp,sum);
        }
        printf("%I64d\n",sum);
    }
    return 0;
}

若有错误,还请指正,谢谢。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值