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位,我们枚举所有数 a j ,假设 a j 第i位为1,就AND a j ,否则AND a j 取反。这样的结果只有第i位和一定与第i位相同的位会是1,将这个结果记为 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上当前这个 b i 依然不会大于当前限制,那么不OR这个 b i ,对于任何 b j ( j > i ) ,因为它的第i位及更高位不可能是1,所以OR任意多个也不会比OR b i 的结果大,所以不OR b i 的话会有 2 t o t i 个新的方案,继续考虑OR b i 的方案数,就让nowOR一下 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;
}

猜你喜欢

转载自blog.csdn.net/litble/article/details/80641262
今日推荐