首页 > 其他分享 >[Tkey] 与非

[Tkey] 与非

时间:2024-06-23 20:53:58浏览次数:30  
标签:return int Tkey fa 与非 集合 operatorname id

解法原理1

首先我们需要明白 \(\operatorname{nand}\) 的运算:

\[\operatorname{not}(a\operatorname{nand}b)=a\operatorname{and}b\tag{1} \]

这个很好理解,因为 \(\operatorname{nand}\) 就是这么定义的(从中文名字可以看出来)。

\[(\operatorname{not}a)\operatorname{nand}(\operatorname{not}b)=a\operatorname{or}b\tag{2} \]

这个是因为下述式子:

\[\operatorname{not}((\operatorname{not}a)\operatorname{and}(\operatorname{not}b))=a\operatorname{or}b \]

最后:

\[a\operatorname{xor}b=(a\operatorname{and}(\operatorname{not}b))\operatorname{or}((\operatorname{not}a)\operatorname{and}b)\tag{3} \]

因为 \(\operatorname{and},\operatorname{or}\) 我们都表示出来了,因此我们还可以用 \(\operatorname{nand}\) 来表示 \(\operatorname{xor}\)。

所以可以发现 \(\operatorname{nand}\) 实际上包含了全部我们需要的位运算。

解法原理2

拥有了全部的位运算,那么现在我们是不是就能得到所有数字了呢?显然不是,考虑一下的例子:

10101
00000

可以发现我们无论如何操作,都不能使第 \(2\) 位和第 \(4\) 位变成不同的数字,这就是我们这道题唯一的限制:假如在每个数 \(n\) 中都有数位 \(i,j\) 是相同的,那么无论如何选择,在结果中一定有 \(i=j\)。

回到刚才的例子,可以发现第 \(1,3,5\) 位也无法在结果中变成不同的数字,发现其实不一定要全部相等,而是每个数中的各位相等即可,因此归纳出本题的限制条件:

若在全部的 \(n\) 个数中都有第 \(i\) 位与第 \(j\) 位相等,那么在结果中也会如此

因此,本题首先需要我们求解出具有限制条件的数位,我们可以使用并查集来维护。

代码步骤1

因为本题 \(n,k\) 较小,可以考虑暴力枚举进行合并。

我们每次枚举两个位置 \(i,j\) 判断这两个位置是否对全部的 \(n\) 个数都满足上述条件,如果是,那么我们就合并这两个数。最后合并到一起的几个数就是符合条件,并且范围最大的结果。

请注意此处并查集合并操作的合并顺序,具体用处请见解法原理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

求出全部“相互独立的数”之后,我们现在需要求解答案了。

首先考虑统计出当前并查集中的集合个数 \(a\),这 \(a\) 个集合相互独立,互不影响,并且每个集合都可以独立地拼凑出 \(1\) 或 \(0\)(刚才的解法原理1证明了,这样的拼凑总是可能的)。因此方案数应该为 \(2^{a}\)。

现在我们来考虑边界问题。我们可以很容易地将边界 \([l,r]\) 转化成求 \([1,r]-[1,l-1]\),这样我们就只需要处理右边界了。下面我们可以把问题转化成一个数位 DP。

假如右边界 \(x\) 的第 \(i\) 位数为 \(0\),说明这一位可能受到了限制(相当于数位 DP 里的 \(limit\)),例如 x=010010 ,其右数第 \(3\) 位为 \(0\),这说明在前几位填 010 的时候,这一位实际上只能填 \(0\) 了,因为如果填 \(1\) 就超出右边界范围了。

同时,因为同一个集合内的元素一定会相等,因此假如集合内任何一个元素受到了限制,那么整个集合都会因此受到限制。基于这个想法,我们不妨来维护一个数组 \(limit_{i}\),来判断 \(i\) 是否受到了这样的限制。

我们需要寻找出 \(x\) 从右往左第一个不受到限制的集合,并从它的末尾开始统计答案(因为实际上去除最右方的首个受限集合后,剩余的集合就不会再受到限制了,读者不妨自己试举几例)。将限制全部都转移到代表元素上,可以基本总结出下面的流程:

  1. 假设每个集合的代表元素都在集合的最左边(这很好实现,你只需要在实现并查集的时候将靠右的元素合并到靠左的位置即可)。
  2. 从右至左依次遍历右边界 \(x\) 的每一位 \(x_{i}\)。
  3. 假如 \(x_{i}=1\),并且此时代表元素未受到限制,且该集合尚未访问过,则统计答案。统计后将该集合标记为已访问。
  4. 假如 \(x_{i}=0\),将该集合标记为受限,若该集合已经访问过,则退出循环,表示找到了此位置。
  5. 假如遇到代表元素受限的元素,说明这个受限的集合已经结束,也退出循环,表示找到了此位置。

现在我们通过此流程找到了第一个不受限制的数位 \(p\),并且 \(p\) 以前的集合有 \(s_{p}\) 个,那么总方案数就为 \(2^{s_{p}}\)。

根据上式可以看出,为了计算这个答案,实际上我们还需维护一个集合数量的前缀数组。

代码步骤2

首先我们考虑到,假如右边界 \(x\ge 2^{k}-1\),那么它一定能够包含全部的 \(2^{a}\) 种情况,这是最简单的。

剩下的步骤即为解法原理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);

代码实现

AC Record

#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);
}

标签:return,int,Tkey,fa,与非,集合,operatorname,id
From: https://www.cnblogs.com/HaneDaCafe/p/18263890

相关文章

  • 操作系统的发展史、多道技术、进程理论、进程的三状态、同步异步/阻塞与非阻塞、开启
    【操作系统发展史】1为什么要使用操作系统呢?2程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作,3这个繁琐的工作就是操作系统来干的,有了他,程序员就从这些繁琐的工作中解脱了出来,4只需要考虑自己的应用软件的编写就可以了,应用软件......
  • 在 C# 中对比KeyValuePair<TKey, TValue> 和 IDictionary<TKey, TValue>
    C#中的KeyValuePair<TKey,TValue>和IDictionary<TKey,TValue>具有独特的用途并表现出不同的特征。KeyValuePair<TKey,TValue>的功能KeyValuePair<TKey,TValue>是存储单个键值对的数据结构。它属于System.Collections.Generic命名空间。用法它用于表示单个......
  • [Tkey] A decorative fence
    还是看看简单而富有美感的爆搜吧#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong#definetestsintcases;cin>>cases;while(cases--)intn,l;vector<int>e;boolvis[21];intcnt=0;voiddfs(intp){ if(cnt==l)return; if(p>n){ cnt++......
  • 编码与加密(对称加密与非对称加密)
    目录编码与加密Base64编码(可逆)十六进制编码(hex.EncodeToString函数)(可逆)哈希算法(不可逆)MD5(不可逆)SHA-256(不可逆)MAC算法(不可逆)加密算法(可逆)对称加密算法(可逆)DES(可逆)AES(可逆)区别非对称加密算法(可逆)RSA(可逆)ECC(可逆)PEM格式存储密钥DER格式存储密钥加密模式CB......
  • 从零开始的模拟集成电路设计(2):软件的使用与二输入与非门的设计仿真
     从零开始的模拟集成电路设计(1):软件的使用与简单数字集成电路的设计仿真-CSDN博客上接前文:我们在前面的课程中已经学会了如何设计一个简单的数字集成电路:反向器,现在我们继续学习下一个非常实用的数字集成电路:与非门。学习目的:1.掌握集成电路模拟仿真的基本流程2.掌握集成电......
  • [Tkey] 生日礼物
    题意简述彩珠有\(n\)个\(k\)种,每个珠子都有一个坐标\(p_{i}\),求最小的区间长度,使得这个区间包含全部的\(k\)种彩珠.分析发现我们可以维护每一种颜色的最近出现坐标.因为是最近的出现坐标,所以离现在的距离(即答案)一定是更优的,那么我们用这个值来更新答案一定就是最优的.......
  • Kotlin可空类型与非空类型以及`lateinit` 的作用
    Kotlin可空类型与非空类型以及lateinit的作用在Kotlin中,变量可以是可空类型或非空类型。可空类型表示变量可以包含一个空值(null),而非空类型表示变量不能包含空值。可空类型与非空类型非空类型:默认情况下,Kotlin中的变量是非空类型。例如,varrecyclerView:RecyclerView表......
  • [Tkey] CodeForces 1267G Game Relics
    太神了这题,膜拜出题人orz。思考一首先是大家都提到的一点,先抽卡再买。这里来做个数学分析。假设我们还剩\(k\)种没有买,其实我们是有式子来算出它的花费期望的。WIKI上提到,假设一个事件的概率为\(p\),则遇到它的期望为\(\frac{1}{p}\),因此,对于这个题,抽到一个新物品的概率显......
  • 翻译《The Old New Thing》- Hotkeys involving the Windows logo key are reserved b
    HotkeysinvolvingtheWindowslogokeyarereservedbythesystem-TheOldNewThing(microsoft.com)https://devblogs.microsoft.com/oldnewthing/20071130-00/?p=24333RaymondChen 2007年11月30日Windows徽标键的热键由系统保留        系统保留了......
  • net 静态方法与非静态方法
    usingSystem;namespaceConsoleApp1{publicclassProgram{/*静态方法(static):特点:1.生命周期,一旦创建--应用结束才会销毁2.可全局使用3.效率高用处:用户登陆信息,系统......