[JZOJ4937]与运算

本文介绍了一种利用位运算和动态规划解决特定序列重新排列问题的方法,旨在求得序列的最大价值。通过分析序列中按位与运算的特点,采用分治策略计算序列元素出现次数,并利用动态规划寻找最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

对于一个序列 a1,a2,...,an ,定义 fi 表示序列前 i 项依次进行按位与运算后的值。定义一个序列的价值为ni=1fi
现在给定一个序列 a1,a2,...,an ,你需要把它重新排列,求序列的最大价值。

1n,ai1000000


题目分析

可以发现, fi 随着 i 的增大在不断地变小,变成它的子集(将二进制看成集合)。
因此fi形成了若干个值相同的块,并且前面的块的值包含了后面的块的值。而且最后一块的值一定是整个序列的与值。
考虑使用 dp 来计算答案,令 Fi 表示 f 值为i时的最大价值。可以写出:

Fi=maxij{Fj+i×(cnticntj)}

在这里, cnti 表示包含二进制 i ai个数。从上一块到当前块,我们将 i 作为当前块的值,那么其造成的贡献显然是i×(cnticntj)
可是这样直接计算是 O(n2) 的。考虑枚举子集来转移,那么时间复杂度是 O(nlog23) 的。
怎样才能更快呢?其实我们只需要从只比 i 多一位1 j 转移到i就好了。枚举二进制位时间 O(nlogn)
这样会不会有弄不出来的值成了最优答案呢?其实不会,因为如果这个值弄不出来,要么就包含能够弄出来的数,这样它一定是作为转移的中间媒介,不可能最优,要么是完全弄不出来的数,那它的值一定是 0
至于计算cnti,就是经典的分治思路了,在这里不讲,参考 [COCI2012]toy
总时间复杂度 O(nlogn)


代码实现

#include <iostream>
#include <cstdio>
#include <cctype>

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=1000005;
const int A=1<<20;

int cnt[A];
int a[N];
LL f[A];
int n,sum;

void count(int l,int r)
{
    if (l==r) return;
    int mid=l+r>>1;
    for (int i=mid+1;i<=r;i++) cnt[l+(i-mid)-1]+=cnt[i];
    count(l,mid),count(mid+1,r);
}

void dp()
{
    f[A]=1;
    for (int s=A-1;s>=0;s--)
        for (int i=0;i<20;i++)
            if (!(s&(1<<i)))
                f[s]=max(f[s],f[s|(1<<i)]+1ll*s*(cnt[s]-cnt[s|(1<<i)]));
}

int main()
{
    freopen("and.in","r",stdin),freopen("and.out","w",stdout);
    n=read(),sum=A-1;
    for (int i=1;i<=n;i++) cnt[a[i]=read()]++,sum&=a[i];
    count(0,A-1),dp();
    printf("%lld\n",f[sum]);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值