bzoj2728/洛谷P3220 与非 (伪)线性基+(伪)数位dp

做题的时候可能脑子里有根神经烧断了,把“与非”看成了“异或”,心头一喜,直接线性基一套,惨烈爆0。
不过线性基的思路还是很好的,与非能不能弄一个类似线性基的东西出来呢?

线性基是什么?
线代大佬一定很清楚,不过我是一个古代蒟蒻,所以不是很清楚。
现在我们有一堆数,我们要对他们随意做一个位运算,最后一定能得到若干不同的结果,形成结果集S。可能二进制位中有这么两位i和j,i取值定下来了,j的取值一定会定下来,才能保证这个数在S集中。
我们用我们可以使用的那个位运算操作整出若干个数,这若干个数可以操作运算得到(或曰:张成)结果集S,那么古代蒟蒻也不知道这个定义是否科学,我们暂时把这若干个数称为(伪)线性基

与非是什么?
与非这个操作很神奇,因为:
NOT x=x NAND x ,所以NOT可以用与非表示。
x AND y=NOT(x NAND y) ,AND也可以被与非表示,然后所有的位运算操作都可以被表示了,譬如:
x OR y=(NOT x) NAND (NOT y)x XOR y=((NOT x)AND y)OR(x AND (NOT y))

显然,如果有两位i和j,在所有数中,第i位和第j位都一样,那么任何结果中,第i位和第j位都一样。
而其他的位就不受这样的约束条件了,因为我们可以这么构造(伪)线性基:对于第i位,我们枚举所有数 aj a j ,假设 aj a j 第i位为1,就AND aj a j ,否则AND aj a j 取反。这样的结果只有第i位和一定与第i位相同的位会是1,将这个结果记为 bi b i 。代码表示如下:

for(RI i=K-1;i>=0;--i)
    if(!vis[i]) {
        b[++tot]=bin[K]-1;//bin[i]:2的i次方
        for(RI j=1;j<=n;++j)
            if(a[j]&bin[i]) b[tot]&=a[j];
            else b[tot]&=(a[j]^(bin[K]-1));
        for(RI j=0;j<i;++j)
            if(b[tot]&bin[j]) vis[j]=1;
    }

这样操作后,我们得到了若干个数。由于OR操作也是可以用NAND进行的,假如我们要构造一个哪几位是1,而这几位之间没有约束的数,就直接将这些b给OR起来即可。

现在开始求解,问题转化为小于等于R的结果数-小于等于L的(建议不看语言描述直接看代码理解)。
从高位往低位考虑。假设当前得到的结果是now,OR上当前这个 bi b i 依然不会大于当前限制,那么不OR这个 bi b i ,对于任何 bj(j>i) b j ( j > i ) ,因为它的第i位及更高位不可能是1,所以OR任意多个也不会比OR bi b i 的结果大,所以不OR bi b i 的话会有 2toti 2 t o t − i 个新的方案,继续考虑OR bi b i 的方案数,就让nowOR一下 bi b i 。假如会大于当前限制,肯定不能OR,跳过即可。

LL dp(LL x) {
    if(x==-1) return -1;
    LL now=0,re=0;
    for(RI i=1;i<=tot;++i)
        if((now|b[i])<=x) now|=b[i],re+=bin[tot-i];
    return re;
}

完整代码:

#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
LL L,R,ans,now;int n,K,js,tot;
LL bin[66],a[1005],b[66];int vis[66];
LL dp(LL x) {
    if(x==-1) return -1;
    LL now=0,re=0;
    for(RI i=1;i<=tot;++i)
        if((now|b[i])<=x) now|=b[i],re+=bin[tot-i];
    return re;
}
int main()
{
    scanf("%d%d%lld%lld",&n,&K,&L,&R);
    for(RI i=1;i<=n;++i) scanf("%lld",&a[i]);
    bin[0]=1;for(RI i=1;i<=K;++i) bin[i]=bin[i-1]<<1LL;
    for(RI i=K-1;i>=0;--i)
        if(!vis[i]) {
            b[++tot]=bin[K]-1;
            for(RI j=1;j<=n;++j)
                if(a[j]&bin[i]) b[tot]&=a[j];
                else b[tot]&=(a[j]^(bin[K]-1));
            for(RI j=0;j<i;++j)
                if(b[tot]&bin[j]) vis[j]=1;
        }
    printf("%lld\n",dp(R)-dp(L-1));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值