CSP-J初赛知识2
逻辑运算与位运算
1. 逻辑学
简介:
逻辑学有广义和狭义之分。狭义的逻辑学指:研究推理的科学,即只研究如何从前提必然推出结论的科学。广义的逻辑学指:研究思维形式,思维规律和思维的逻辑方法的科学。广义逻辑学研究的范围比较大,是一种传统的认识,与哲学研究有很大关系。整个逻辑学科的体系非常庞大复杂,如:传统的、现代的和辩证的、演绎的、归纳的和类比的、经典的和非经典的,等等。但是,它再庞杂也有相通的地方,例如:构建判断的方法;进行必然性推理;认同逻辑真理或逻辑规律等。
逻辑学作为一门科学的逻辑,是既古老又年轻的。历史悠久,源远流长。它有三大源泉:古希腊的形式逻辑,中国先秦的名辩逻辑,古印度的因明。
逻辑学是一门基础性的学科,逻辑学的基本理论是其他学科普遍适用的原则和方法。同时逻辑学又是一门工具性的学科,它为包括基础学科在内的一切科学提供逻辑分析、逻辑批判、逻辑推理、逻辑论证的工具。
例如,所有语言都是传递信息的,汉语是一种语言,所以,汉语是传递信息的。
在这个论断中,“所有”,“语言”,“传递信息”,“是”,“一种”,“汉语”等是概念。由概念组成的语句,如“所有语言都是传递信息的”等等的内容称作判断。而由判断组成的论断称作推理。
思维的这几种基本形式又由其构成的元素和其联结方式(结构)不同而形成各种不同的亚形式,我们把这类亚形式,即思维内容各组成部分(或元素)的联结方式(即结构),称作思维的逻辑形式(或思维的形式结构)。
例如,所有股票都是有价格的;所有生物都是进化的;所有法律都是具有强制性的。
这三个判断所表达的思想内容是不同的,它们分别陈述了经济学、生物学和法学领域不同对象所具有的某种属性,但是它们在结构上却是相同的,我们说它们具有相同的逻辑形式。我们用“S”表示每个判断中所陈述的对象,用“P”表示对象所具有的属性。于是,上述三个判断的共同逻辑形式是“所有S都是P”,它是判断形式中的一种类型。
再如,
1)如果两个角是对顶角,那么这两个角就相等。这两个角是对顶角,所以,这两个角相等。
我们用“p”表示“如果”后面的判断,用“q”表示“那么”后面的判断。于是,上述两个内容不同的论断,却有着共同的逻辑形式:如果p,那么q;p,所以,q。这也是推理形式的一个种类。
从上述分析可看出,每一种逻辑形式都包含逻辑常项和变项。逻辑常项是指同 类逻辑形式中不变的部分。如上例中的“所有,都是”和“如果,那么”等。逻辑常项决定各种逻辑形式的性质,是区别不同逻辑形式的依据。变项是逻辑形式中的 可变部分,即用拉丁字母表示的那部分。它们可以用相应的具体概念或判断代入。前例中的“S”和“P”是概念变项,它们可以代入任意概念;前例中的“q”和“p”是判断变项,也可代入任意判断。
逻辑学研究概念、判断和推理,不研究具体的思维内容,而是暂时抛开具体内容,研究其逻辑形式及各种逻辑形式之间的关系。
2. 逻辑学中一些重要的逻辑符号
符号 | 名字 | 解说 | 例子 | 读作 | 范畴 |
⇒ | 实质蕴涵 | A ⇒ B 意味着如果 A 为真,则 B 也为真;如果 A 为假,则对 B 没有任何影响。 | x = 2 ⇒ x² = 4 为真,但 x² = 4 ⇒ x = 2 一般为假(因为 x 可以是 −2)。 | 蕴涵;如果.. 那么 | 命题逻辑 |
→ | |||||
⊃ | 可能意味着同 ⇒ 一样的意思(这个符号也可以指示超集)。 | ||||
⇔ | 实质等价 | A ⇔ B 意味着 A 为真如果 B 为真,和 A 为假如果 B 为假。 | x + 5 = y +2 ⇔ x + 3 = y | 当且仅当;iff | |
↔ | |||||
¬ | 逻辑否定 | 陈述 ¬A 为真,当且仅当 A 为假。 | ¬(¬A) ⇔ A | 非 | |
/ | 命题逻辑 | 穿过其他算符的斜线同于在它前面 放置的"¬"。 | x ≠ y ⇔ ¬(x = y) | ||
∧ | 逻辑合取 | 如果 A 与 B 二者都为真,则陈述 A ∧ B 为真;否则为假。 | n < 4 ∧ n >2 ⇔ n = 3(当 n 是自 然数的时候)。 | 与 | |
∨ | 逻辑析取 | 如果 A 或 B有一个为真陈述 或二者均为真陈述,则 A ∨ B 为真;如果二者都为假,则 陈述为假。 | n ≣ 4 ∨ n ≢ 2 ⇔ n ≠ 3(当 n 是 自然数的时候)。 | 或 | |
⊕ | xor | 陈述 A ⊕ B 为真,在要么 A 要么 B 但不是二者为真的时候为真。A ⊻ B 意思相同。 | (¬A) ⊕ A 总是真,A ⊕ A 总是假。 | 异或 | 命题逻辑, 布尔代数 |
⊻ | |||||
∀ | 全称量词 | ∀ x: P(x) 意味着所有的 x 都使 P(x) 都为真。 | ∀ n ∈ N(n² ≣ n). | 对于所有; 对于任何;对于每个;任意的 | 谓词逻辑 |
∃ | 存在量词 | ∃ x: P(x) 意味着有至少一个 x 使 P(x) 为真。 | ∃ n ∈ N(n 是偶数)。 | 存在着 | |
∃! | 唯一量词 | ∃! x: P(x) 意味着精确的有一个 x 使 P(x) 为真。 | ∃! n ∈ N(n + 5 = 2n). | 精确的存在一个 | |
:= | 定义 | x := y 或 x ≡ y 意味着 x 被定义为 y 的另一个名字(但要注意 ≡ 也可以意味着其他东西,比如全等)。 | cosh x := (1/2)(exp x + exp (−x)) | 被定义为 | 所有地方 |
≡ | |||||
:⇔ | P :⇔ Q 意味着 P 被定义为逻辑等价于 Q。 | A XOR B :⇔ (A ∨ B) ∧ ¬(A ∧ B) | |||
() | 优先组合 | 优先进行括号内的运算。 | (8/4)/2 = 2/2 = 1, 而 8/(4/2) = 8/2 = 4。 | 无 | |
├ | 推论 | x ├ y 意味着 y 推导自 x。 | A → B ├ ¬B → ¬A | 推论或推导 | 命题逻辑, 谓词逻辑 |
优先级:括号>非>与>或
3. 信息学中一些重要的逻辑符号
符号 | 名字 | 解说 | 例子 | 读作 |
! | 非 | 存在于if判断中,if(a!=0) 表示当a不等于0时条件成立 | | 非 |
&& | 与 | 存在于if判断中,表示两者皆为真时条件成立 | | 与 |
|| | 或 | 存在于if判断中,如果 A 或 B有一个为真陈述或二者均为真陈述,则条件成立 | | 或 |
4. 位运算
按位取反(~)
1. 含义
按位取反就是将一个数的补码的所有数位取反(包括符号位)。
2. 秘诀
一个负数按位取反则答案为把这个负数的负号去掉变成正数后再 -1。
例如-5取反是4。
一个正数按位取反则答案为先把这个正数+1再加一个负号。
3. 代码实现:
#include<bits/stdc++.h>
using namespace std;
signed main()
{
int a=~4;
int b=~-5;
cout<<a<<" "<<b;
return 0;
}
输出:-5 4
按位与(&)
1. 含义
两个数值间的双目运算符,它将两个数的补码对齐低位,逐位比较,有两个1的数位结果位为1,否则为0。
2. 作用
一般大家用它来判断奇偶,比模运算耗时更短,将一个数&1,如果答案为1则这个数是奇数,否则是偶数
3. 代码实现
#include<bits/stdc++.h>
using namespace std;
signed main()
{
int a=9;
if(a&1==1)
{
cout<<"ji";
}
else
{
cout<<"ou";
}
return 0;
}
输出:ji
按位或( | )
1. 含义
按位或也是双目运算符,它将两个补码对齐低位,逐位比较,有至少1个1的数位结果位为1,否则为0。
2. 代码实现
#include<bits/stdc++.h>
using namespace std;
signed main()
{
int a=3, b=9,c;
c=a|b;
cout<<c;
return 0;
}
输出:11
按位异或(^)
1. 含义
按位或也是双目运算符,它将两个补码对齐低位,逐位比较,两个数位上数值不同结果位为1,否则为0。
2. 代码实现:
#include<bits/stdc++.h>
using namespace std;
signed main()
{
int a=114, b=514,c;
c=a^b;
cout<<c;
return 0;
}
输出:624
优先级
括号>逻辑运算>位运算
括号>按位取反>按位与>按位或=按位异或
线性数据结构
1. 队列
简介:
队列(queue)是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
顺序队列
建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置,如图所示
每次在队尾插入一个元素时,rear增1; 每次在队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元。
顺序队列中的溢出现象:
(1) "下溢"现象:
当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。
(2)"真上溢"现象:
当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。
(3)"假上溢"现象:
由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为"假上溢"现象。
循环队列
在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1或front指针增1 时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。自己真从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。除了一些简单应用之外,真正实用的队列是循环队列。
在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件时front=rear,而队列判满的条件时front=(rear+1)%MaxSize。
优先队列
优先队列(priority_queue)是0个或多个元素的集合,优先队列把每个进入队列的数字从大到小排序,对优先队列执行的操作有
1) 查找;
2) 插入一个新元素;
3) 删除.
双端队列
deque(double-ended queue,双端队列)是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,相比list增加[]运算符重载。
双端队列是限定插入和删除操作在表的两端进行的线性表。这两端分别称做端点1和端点2。也可像栈一样,可以用一个铁道转轨网络来比喻双端队列。在实际使用中,还可以有输出受限的双端队列(即一个端点允许插入和删除,另一个端点只允许插入的双端队列)和输入受限的双端队列(即一个端点允许插入和删除,另一个端点只允许删除的双端队列)。而如果限定双端队列从某个端点插入的元素只能从该端点删除,则该双端队列就蜕变为两个栈底相邻的栈了。
代码模拟:
1. 普通队列
#include<bits/stdc++.h>
using namespace std;
signed main()
{
queue<int> q;
for(int i=1;i<=9;i++)
{
q.push(i);//在队尾插入元素i
}
int len=q.size();//读取队列长度
cout<<len<<" ";
if(q.empty()==false)//队列不为空则条件成立
{
cout<<10086<<" ";
}
while(len--)
{
cout<<q.front()<<" ";//输出队首
q.pop();//弹出队首
}
return 0;
}
输出:9 10086 1 2 3 4 5 6 7 8 9
2. 优先队列
#include<bits/stdc++.h>
using namespace std;
signed main()
{
priority_queue<int> q;
for(int i=1;i<=9;i++)
{
q.push(i);//在队尾插入元素i
}
q.push(498);
q.push(10);
int len=q.size();//读取队列长度
cout<<len<<" ";
if(q.empty()==false)//队列不为空则条件成立
{
cout<<10086<<"\n";
}
cout<<"1 2 3 4 5 6 7 8 9 498 10\n";
while(len--)
{
cout<<q.top()<<" ";//输出队首
q.pop();//弹出队首
}
return 0;
}
输出:
11 10086
1 2 3 4 5 6 7 8 9 498 10
498 10 9 8 7 6 5 4 3 2 1
3. 双端队列
#include<bits/stdc++.h>
using namespace std;
signed main()
{
deque<int> q, qq;
for(int i=1;i<=9;i++)
{
q.push_back(i);//在队尾插入元素i
qq.push_back(i);
}
q.push_front(498);
qq.push_front(498);
q.push_front(10);
qq.push_front(10);
int len=q.size();//读取队列长度
cout<<len<<" "<<len<<" ";
if(q.empty()==false)//队列不为空则条件成立
{
cout<<10086<<"\n";
}
cout<<"498 10 1 2 3 4 5 6 7 8 9\n498 10 1 2 3 4 5 6 7 8 9\n";
while(len--)
{
cout<<q.front()<<" ";//输出队首
q.pop_front();//弹出队首
}
len=qq.size();
while(len--)
{
cout<<qq.back()<<" ";
qq.pop_back();
}
return 0;
}
输出:
11 11 10086
498 10 1 2 3 4 5 6 7 8 9
498 10 1 2 3 4 5 6 7 8 9
10 498 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 498 10
2. 栈
简介
要搞清楚这个概念,首先要明白”栈“原来的意思,如此才能把握本质。栈,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。
首先,系统或者数据结构栈中数据内容的读取与插入(压入)push和 弹出pop是两回事。压入是增加数据,弹出是删除数据 ,这些操作只能从栈顶即最低地址作为约束的接口界面入手操作 ,但读取栈中的数据是随便的,没有接口约束之说。很多人都误解这个理念从而对栈产生困惑。而系统栈在计算机体系结构中又起到一个跨部件交互的媒介区域的作用 即 cpu 与内存的交流通道 ,cpu只从系统给我们自己编写的应用程序所规定的栈入口线性地读取执行指令, 用一个形象的词来形容它就是pipeline(管道线、流水线)。cpu内部交互具体参见 EU与BIU的概念介绍。
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
栈可以用来在函数调用的时候存储断点,做递归时要用到栈。
以上定义是在经典计算机科学中的解释。
在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在i386机器中,栈顶由称为esp的寄存器进行定位。压栈的操作使得栈顶的地址减小,弹出的操作使得栈顶的地址增大。
栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:
1.函数的返回地址和参数
2. 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
单调栈:
单调栈其实是自己用栈模拟的一种结构。
例题:洛谷 P2866 [USACO06NOV] Bad Hair Day S
代码:(杜绝抄袭!)
#include<bits/stdc++.h>
using namespace std;
stack<int> stk;
int main()
{
int n, h[80005];
long long ans=0;
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
while(stk.empty()==false&&stk.top()<=x)
{
stk.pop();
}
ans+=stk.size();
stk.push(x);
}
cout<<ans;
return 0;
}
用代码模拟栈:
模版题:B3614 【模板】栈
代码:(拒绝抄袭!!!)
#include<bits/stdc++.h>
using namespace std;
int main()
{
unsigned long long t;
cin>>t;
while(t--)
{
unsigned long long t1;
cin>>t1;
stack<unsigned long long> stk;
while(t1--)
{
string s;
cin>>s;
unsigned long long a;
if(s=="push")
{
cin>>a;
stk.push(a);
}
if(s=="pop")
{
if(stk.empty()==true)
{
cout<<"Empty"<<endl;
}
else
{
stk.pop();
}
}
if(s=="query")
{
if(stk.empty()==true)
{
cout<<"Anguei!"<<endl;
}
else
{
cout<<stk.top()<<endl;
}
}
if(s=="size")
{
cout<<stk.size()<<endl;
}
}
}
return 0;
}
3. 链表
链表是一种非连续、非顺序的存储结构,每一个“数据”就像就像锁链的一环,每一个环节会连向下一个环节。链表的访问效率不高,因为只能从“表头”顺藤摸瓜,空间利用效率很高,因为不需要连续地址存储。
冒泡排序
1. 含义
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
2. 原理
-
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
时间复杂度为O(n*n)
稳定性
稳定性是指两个相同的元素在排序之后是否有可能交换位置,若不会,则有稳定性,冒泡排序具有稳定性。
DAY 7 学习日记
跟昨天一样,手打了5h。