[BZOJ 2728][HNOI 2012]与非(并查集+计数问题)

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2728

思路

其实这个与非真的很神奇啊,它可以覆盖与、或、非、亦或四种运算,这个可以手玩出来(本渣太懒没试过。。。)。那么这样的话,不管 Ai 中某一位是0还是1,每个 Ai 选了多少次,最终的答案中的这一位既有可能是1,也有可能是0,但是某个数是否能被与非出来还是有限制的,观察到如果对于任意的 Ai,1<=Ai<=n 而言, Ai(2)[a]=Ai(2)[b](Aiab) ,那么最终无论如何与非,得到的答案的第 ab 位一定是相同的,这个也可以手玩出来(本渣太懒没试过=_=b)。
那么我们可以暴力枚举1到k上的两位 a,b,a<b (反正k很小, k2 复杂度几乎可以无视掉),通过并查集合并掉所有相同的列(为了便于叙述,以下均称相同的列组成的玩意是集合), belong[i]=i 所在的集合。同时可以求出 num= 集合的个数。
然后我们就需要求区间 [L,R] 上能被与非出来的数的个数了。很显然可以想到只需能求出区间 [1,x1] 的合法数个数就行了,写个函数 solve(x) 来求。

n[L,R]([L,R])=solve(R+1)solve(L)

那我们现在只需要关心 solve(x) 的过程了,比较显然的是由于最终可以与非出来的数的个数最多只能是 2num (每个集合可以选1或者0,有2种选法,一共 num 个集合)。而由于限制了二进制数的长度最大为 k ,因此 x>=2k 时,答案就永远是 2num 了,这个是很显然的,这里只需要在最开始加个特判就行。
那么现在的 x 就是合法的了, x1 的位数一定是不超过 k 的,那么我们就要找所有的能与非出来的数 a,1<=a<x 。这是个非常基础的数位DP,我们维护一个数字 tmp= 在还没有被枚举到的位(区间 [1,i] )中尚未确定下来的集合个数。从 x 的高位往低位扫,对于每一位,进行下列操作:

  1. 如果 x 的当前位所属集合是0还是1尚未确定
    (1). x 是1

    (2). x 是0
    标记 x 的当前位所属集合确定是0

  2. 否则 x 的当前位所属集合是0还是1已经确定了
    (1). x 的当前位是1,但是之前确定了它所在的集合都是0,直接退出
    (2). x 的当前位是0,但是之前确定了它所在的集合都是1,直接退出

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1010
#define MAXK 70

using namespace std;

typedef long long int LL;

LL a[MAXN]; //保存A1...An
LL L,R;
int n,k,num=0; //num=集合总数

bool isSame(int x,int y) //判断n个Ai中,对于每个Ai,它的第x位和第y位是否都相同
{
    for(int i=1;i<=n;i++)
        if(((a[i]>>x)^(a[i]>>y))&1)
            return false;
    return true;
}

int belong[MAXK]; //belong[i]=与列i相同的列j,j<i,j=i表示列i是独立不受影响的
int mark[MAXK]; //mark[i]=-1表示尚未确定集合i是什么数字,0表示确定集合i中的每一位都是0,1表示确定每一位都是1

LL solve(LL x) //求区间[1,x-1]的能与非出来的数字个数
{
    if(x>=1LL<<k) return 1LL<<num;//x的长度超出了k
    int tmp=num; //tmp=有多少组集合在1~i位中未确定的
    LL ans=0;
    memset(mark,-1,sizeof(mark));
    for(int i=k-1;i>=0;i--) //枚举x的第i位
    {
        if(mark[belong[i]]==-1) //第i位所属集合没定,第i位是几,第i位所属的集合也定为这个数字
        {
            tmp--;
            if((x>>i)&1) //x的第i位是1
            {
                mark[belong[i]]=1;
                ans+=1LL<<tmp;
            }
            else
                mark[belong[i]]=0;
        }
        else
        {
            if((x>>i)&1)
            {
                if(mark[belong[i]]==0)
                {
                    ans+=1LL<<tmp;
                    break;//x的第i位是1,但是x所属集合已经被确定为了0,直接退出
                }
            }
            else if(mark[belong[i]]==1) break; //x的第i位是0,但是x所属集合已经被确定为了1,直接退出
        }
    }
    return ans;
}

int main()
{
    scanf("%d%d%lld%lld",&n,&k,&L,&R);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=0;i<k;i++)
    {
        bool flag=false;
        for(int j=0;j<i;j++)
            if(isSame(i,j)) //n个A数字中i位和j位都是一样的
            {
                belong[i]=j;
                flag=true;
                break;
            }
        if(!flag)
        {
            num++;
            belong[i]=i;
        }
    }
    printf("%lld\n",solve(R+1)-solve(L));
    return 0;
}

你可能感兴趣的:([BZOJ 2728][HNOI 2012]与非(并查集+计数问题))