[Tkey] 与非
解法原理1
首先我们需要明白
这个很好理解,因为
这个是因为下述式子:
最后:
因为
所以可以发现
解法原理2
拥有了全部的位运算,那么现在我们是不是就能得到所有数字了呢?显然不是,考虑一下的例子:
10101
00000
可以发现我们无论如何操作,都不能使第
回到刚才的例子,可以发现第
若在全部的
因此,本题首先需要我们求解出具有限制条件的数位,我们可以使用并查集来维护。
代码步骤1
因为本题
我们每次枚举两个位置
请注意此处并查集合并操作的合并顺序,具体用处请见解法原理3的流程第一条。
//主函数部分
for(int i=1;i<=k;++i){
fa[i]=i;//初始化
}
for(int i=1;i<=k;++i){
for(int j=i-1;j>0;--j){
//枚举全部可能的 i,j
if(check(i,j)) connect(i,j);
}
}
//并查集板子与 check()
// ---DSU Part---
int fa[61];
int find(int id){
if(fa[id]==id) return id;
fa[id]=find(fa[id]);
return fa[id];
}
void connect(int x,int y){
if(find(x)!=find(y)){
fa[fa[y]]=fa[x];
}
}
// --------------
// Check
bool check(int x,int y){
//检查 x,y 是否符合条件
for(int i=1;i<=n;++i){
if(((a[i]>>x-1)^(a[i]>>y-1))&1)
//上面这句的意思是:判断 a[i] 的第 x,y 位是否相等
return false;
}
return true;
}
解法原理3
求出全部“相互独立的数”之后,我们现在需要求解答案了。
首先考虑统计出当前并查集中的集合个数
现在我们来考虑边界问题。我们可以很容易地将边界
假如右边界 x=010010
,其右数第 010
的时候,这一位实际上只能填
同时,因为同一个集合内的元素一定会相等,因此假如集合内任何一个元素受到了限制,那么整个集合都会因此受到限制。基于这个想法,我们不妨来维护一个数组
我们需要寻找出
- 假设每个集合的代表元素都在集合的最左边(这很好实现,你只需要在实现并查集的时候将靠右的元素合并到靠左的位置即可)。
- 从右至左依次遍历右边界
的每一位 。 - 假如
,并且此时代表元素未受到限制,且该集合尚未访问过,则统计答案。统计后将该集合标记为已访问。 - 假如
,将该集合标记为受限,若该集合已经访问过,则退出循环,表示找到了此位置。 - 假如遇到代表元素受限的元素,说明这个受限的集合已经结束,也退出循环,表示找到了此位置。
现在我们通过此流程找到了第一个不受限制的数位
根据上式可以看出,为了计算这个答案,实际上我们还需维护一个集合数量的前缀数组。
代码步骤2
首先我们考虑到,假如右边界
剩下的步骤即为解法原理3所述。
int ask(int x){
if(++x>=(1ll<<k)){//特殊情况
return (1ll<<s[k]);
}
int ans=0;
memset(limit,-1,sizeof(limit));
//注意清空数组
for(int i=k;i>0;i--){
if(x&(1ll<<i-1)){
if(limit[fa[i]]!=1){
ans+=1ll<<s[i-1];
//统计答案
}
if(fa[i]==i){
limit[i]=1;
//标记访问
}
if(limit[fa[i]]==0){
//步骤5
break;
}
}
else{
//步骤4
if(fa[i]==i){
limit[i]=0;
}
if(limit[fa[i]]==1){
break;
}
}
}
return ans;
}
//主函数的预处理与统计答案部分
for(int i=1;i<=k;i++){
s[i]=s[i-1];
if(find(i)==i){
//预处理集合数量前缀和
s[i]++;
}
}
cout<<ask(r)-ask(l-1);
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,l,r;
int a[1001],s[61],limit[61];
int fa[61];
int find(int id){
if(fa[id]==id) return id;
fa[id]=find(fa[id]);
return fa[id];
}
void connect(int x,int y){
if(find(x)!=find(y)){
fa[fa[y]]=fa[x];
}
}
bool check(int x,int y){
for(int i=1;i<=n;++i){
if(((a[i]>>x-1)^(a[i]>>y-1))&1) return false;
}
return true;
}
int ask(int x){
if(++x>=(1ll<<k)){
return (1ll<<s[k]);
}
int ans=0;
memset(limit,-1,sizeof(limit));
for(int i=k;i>0;i--){
if(x&(1ll<<i-1)){
if(limit[fa[i]]!=1){
ans+=1ll<<s[i-1];
}
if(fa[i]==i){
limit[i]=1;
}
if(limit[fa[i]]==0){
break;
}
}
else{
if(fa[i]==i){
limit[i]=0;
}
if(limit[fa[i]]==1){
break;
}
}
}
return ans;
}
signed main(){
cin>>n>>k>>l>>r;
for(int i=1;i<=n;++i){
cin>>a[i];
}
for(int i=1;i<=k;++i){
fa[i]=i;
}
for(int i=1;i<=k;++i){
for(int j=i-1;j>0;--j){
if(check(i,j)) connect(i,j);
}
}
for(int i=1;i<=k;i++){
s[i]=s[i-1];
if(find(i)==i){
s[i]++;
}
}
cout<<ask(r)-ask(l-1);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!