语法
数据类型
整型:原码、反码与补码
-
首先把整数的真实数值称为真值。
-
原码:最高位表示符号(\(0=+,1=-\)),后面是其绝对值的二进制表示。
-
反码:正数的反码等于原码,负数的反码等于原码除符号位外取反所得。\(0\) 见下。
-
补码:正数和 \(0\) 的补码等于原码,负数的补码等于其反码 \(+1\) 。允许向符号位进位,进位溢出相当于自然溢出(即对类型大小上限取模)。
-
目的:
-
原->反:标记符号位非常麻烦。设法让其参与运算。可以使真值正确,但是 \(0\) 的符号不对(\(00000000\) 或 \(10000000\))。在计算机意义下并不美妙。
-
反->补:平移解决上面的 bug。顺便使得 \(10000000=-128\),这也就是为什么负数比正数多了一个。
-
-
没有规定必须用补码实现整型!!!
-
不过,至少我没见过这种鬼才实现。
整型:大端与小端
-
我们可以把整数的较高位存在较靠前的位上,也可以把较低位存在较靠前的位上。
-
优劣性?目前不了解。据称这是历史遗留问题,现在各种语言/环境(广义的,包括 HTML,MAC,...)仍然使用着不同的表示方式。
-
C++ 中的影响?几乎没有。唯一有影响的可能是高精度类型强转低精度类型的结果,但就我目前的了解:
-
浮点型的强转是降低精度以匹配(一般是抛弃太小的小数)。
-
整形强转无论大小端一定抛弃高位。
-
浮点型:精度
-
__float128
的精度比long double
高。它的位数在事实上对标__int128
,为 \(128\text{bit}\),即 \(16\text{byte}\)。 -
理由?嗯...众所周知,
__
类都不是 ISO C++ 规定中要实现的东西(如果实现,也不规定怎么实现)。 -
但是,
long double
是 ISO C++。<cfloat>
或者说<float.h>
库中有它相关的声明,所以它是一定要实现的。 -
然而它的实现标准很有趣:最坏情况下和
double
完全一样,都是:-
最大值至少为 \(10^{308}\)。
-
最小值(比 \(0\) 大的)至少为 \(10^{-308}\)。
-
精度(定义为能表示的比 \(1\) 大的最小数和 \(1\) 的差)至少为 \(10^{-9}\)。
-
-
换言之,在某些毒瘤实现中,可能
long double
就是double
的等价类,甚至只是一个同义关键字。 -
不过,在较常见的实现中,
long double
要么是 80 bit(15-1-64,分别是 \(2\) 的指数、正负、有效数字部分),要么在 64 bit 内实现了比double
更高的精度(但值域一样)(怎么做到的??)。所以可以期望通过使用它来提高精度——但也可能会导致常数过大而被卡。
浮点型:除零与判等
-
小标题中的除零是一种非法操作(尽管在浮点数中并不非法)的称呼,按照数学语言应该是除以零。
-
好了说回来。众所周知,浮点型除了常规数值之外,还有两个特殊值:\(inf\) 与 \(nan\)。
-
两者怎么实现(或者说二进制下的表示)我不了解,不过两者分别表示的东西我们还是知道的:
-
\(inf\):无穷大。通常来源于非零数除以 \(0\),或 \(inf\) 与非 \(inf\) 数的运算。
-
\(nan\):非法。通常来源于 \(0/0\) 或 \(inf-inf,inf/inf\),以及所有含 \(nan\) 的运算。
-
-
说回判等。受掉精度(可能还有随机扰动)的影响,对浮点型我们通常认为若
fabs(x-y)<eps
,则 \(x=y\)。那么,\(inf\) 和 \(nan\) 呢? -
两个 \(inf\) 彼此相等。这里的相等,指的是对他们做
==
的二元运算会返回 \(true\)。 -
但是!\(inf-inf=nan\),所以
fabs(x-y)<eps
的结果为 \(false\)。 -
故不要指望对竖线用斜率判共线,没戏的。
-
当然,可以加一个
==
的补丁...可话又说回来,万一不保证点彼此不同呢?\(nan\) 参与的所有运算都是非法运算,语句nan>nan,nan==nan,nan<nan
的返回值全部是 \(0\)。 -
所以,如果碰到竖线,请老老实实特判。换言之,记住斜率不是万能的,共线总是得特判!
C 库函数
memset
void * memset ( void * ptr, int value, size_t num );
-
原理是把中间那个数字的十六进制表示(当然是补码意义下的)反复地放到对应数据类型的每 16 位上。
-
对于
int
型,我们通常使用这些:-
\(0\to 0\)。
-
\(-1\to -1\)。
-
\(127\to 2139062143\)。
-
\(128\to -2139062144\)。
-
\(63\to 1061109567\)。
-
\(192\to -1061109568\)。
-
-
最后这两个的目的是加不炸的 \(inf\)。事实上,\(63\) 的结果就是 \(\text{0x3f3f3f3f}\),\(192\) 的结果就是 \(\text{0xc0c0c0c0}\)。
memcpy
void * memcpy ( void * destination, const void * source, size_t num );
- 原理类似 memset,就不多说了。
fill
template <class ForwardIterator, class T> void fill (ForwardIterator first, ForwardIterator last, const T& val);
- 看起来是和上面那两个不一样的原理呢...不过使用起来是一样的。
iota
template <class ForwardIterator, class T> void iota (ForwardIterator first, ForwardIterator last, T val);
- 同上。
unique
unique(iterator first,iterator last)
-
去重 \([first,last)\) 之间的元素(实质是把重复的都放到后面)。复杂度 \(O(n)\)。
-
返回值是去重后的末元素后处指针。
-
如果想要获得末元素的下标,应当这样:
top=unique(a,a+n)-a
。 -
当然也可以从下标 \(1\) 开始,不过减去的时候也要对应减去。
mt19937
mt19937 _rand(chrono::steady_clock::now().time_since_epoch().count()^271828)
-
调用此声明可以创建一个
mt19937
(基于梅森素数 \(2^{19937}-1\) 的随机数类)的随机函数,括号内的东西是种子。 -
这里选择使用高精度时间+扰动作为种子。
-
该随机函数返回值为
int
范围内的自然数,如果需要ll
范围的可以用mt19937_64
。 -
随机性超好而且比
rand()
快。
random_shuffle 与 shuffle
random_shuffle(iterator first,iterator last,RandomNumberGenerator&& gen)
-
第三个参数是我从 C++ Ref 直接搬过来的,所以显得很怪。
-
将 \([first,last)\) 的元素随机打乱。打乱方式和随机化-随机打乱中的算法完全相同,只是将数组下标改为从 \(0\) 开始的。
-
故那个
gen
就是一个随机数发生器罢了。可以省略,但请一定手动实现它!默认的rand()
即使赋了srand()
,表现也非常差。推荐使用mt19937
。 -
具体来讲,第三个参数应当是一个如下的函数:
il int myrand(int i){return _rand()%i;}
- 即读入一个
int
\(i\),返回一个 \([0,i)\) 之间的随机数。调用时应该这么写:
random_shuffle(first,last,myrand)
-
是的,
myrand
不加括号。 -
需要指出的是,这里之所以是
int
而非整型的主要理由是不可能开出一个长度炸int
的数组(bitset
能不能random_shuffle
我没试过)。 -
但同样需要指出的是,
rand()
的实现只要求它返回一个 \([0,32768)\) 之间的整数,所以在数组大小大于这个上界的时候,不加RandomNumberGenerator
的打乱效果奇差无比。 -
shuffle
的效果据称比random_shuffle
好很多,但我不了解其内部实现。就使用而言,这样:
shuffle(first,last,_rand)
- 嗯把那个
mt19937
声明的随机函数不加括号地传进去就好。
字典序(姑且放这里)
For(i,0,min(a.size(),b.size())-1)
if(a[i]!=b[i])
return a[i]<b[i];
return a.size()>b.size();
STL 容器
priority_queue
priority_queue<类型名,容器<类型名>(,比较结构体)>
-
其中的容器可以使用 vector 或 deque,通常使用前者因为 deque 非常慢而且空间大。
-
默认为大根堆(越小的越靠后),想改变可以传 cmp 或者逆向重载 < 运算符。
-
如果是定义了小于号的类型,可以不要比较结构体。这是较为常用的方式。
-
如果想用比较结构体,譬如说想对
struct number
实现多个比较逻辑不同的priority_queue
:实现一个struct
,内置一个operator()
的实现。
struct number{
int k; ll v;
};
struct cmpa{
il bool operator()(number a,number b){return a.v!=b.v?a.v<b.v:a.k>b.k;}
};
struct cmpb{
il bool operator()(number a,number b){return a.v!=b.v?a.v<b.v:a.k<b.k;}
};
- 复杂度:\(O(\log_2 n)\)。
set 与 multiset
-
s.~set()
或s.~multiset()
:销毁集合,释放内存。 -
不建议使用,因为 set 与 multiset 总是在元素被删除时释放对应内存,clear 可以起到相同效果。
-
相较之下,解构后的集合相当于没有被声明过(即之后任意的调用都是 UB),首先不能多组数据重复使用,然后有可能导致莫名 RE。
u_map 与 u_set
- 自定义哈希函数:
struct my_hash{
static ull splitmix64(ull x){
x+=0x9e3779b97f4a7c15;
x=(x^(x>>30))*0xbf58476d1ce4e5b9;
x=(x^(x>>27))*0x94d049bb133111eb;
return x^(x>>31);
}
size_t operator()(ull x) const {
static const ull FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
-
第一个函数无所谓,就是一个固定的哈希生成器。第二个是需要实现的哈希函数。将此结构体作为最后一个参传入对应 STL 即可。
-
对,就是 splitmix64 算法...虽然是公开的,但考虑到它优秀的哈希强度和 chrono 带来的随机性,应该没人卡得掉?
vector 与 basic_string
-
emplace_back
比push_back
快。 -
vector 远比你想象得占空间。用 size 命令实测得到空 vector 会占 24 个类的空间。
内置的重载运算符
bool operator<(const 结构体名 &b) const{
if(r==b.r) return l<b.l;
return r<b.r;
}
-
随便举了个例子。
-
其中的
l,r
可以写成this->l,this->r
(倒不如说这其实是简写)。 -
this
是本元素的指针,&
的那个是右边那个。对于 += 等运算符也是一样的。
高精度
-
原理为竖式加减,高精除高精例外(目前已知的(实践意义的)实现基于二分+减法模拟除法)。
-
下面给出一份可用的压位实现,缺陷:未实现高精除高精,对任意进制的处理很差(各种只能转 \(10^9\) 进制的函数没有标明),末尾的转二进制输出很迷惑,宏定义已经过期(其中的 For 现在应为 Fort)。
namespace bigint_support{
const ll M=1e9;
const int maxlen=2e3,uselen=2e3;
struct bigint{
ll a[maxlen]; int len;
bigint(){memset(a,0,sizeof(ll)*uselen),len=0;}
il bool empty()const{return !len;}
il ld lg()const{return len?(len-1)*9+log10((long double)a[len-1]):0;}
};
il void big9rd(bigint &A){
A=bigint();
static string in; cin>>in;
for(int i=in.size()-1,j;;i=j-1,++A.len){
j=mymax(0,i-8);
For(l,j,i) A.a[A.len]=(A.a[A.len]<<3)+(A.a[A.len]<<1)+(in[l]^48);
if(!j) break;
}
A.len+=A.a[A.len]>0;
return;
}
il bigint big9rdr(){
static bigint ret; ret=bigint();
static string in; cin>>in;
for(int i=in.size()-1,j;;i=j-1,++ret.len){
j=mymax(0,i-8);
For(l,j,i) ret.a[ret.len]=(ret.a[ret.len]<<3)+(ret.a[ret.len]<<1)+(in[l]^48);
if(!j) break;
}
ret.len+=ret.a[ret.len]>0;
return ret;
}
il void l2big(bigint &A,ll B){
A=bigint();
for(;;++A.len){
A.a[A.len]=B%M,B/=M;
if(!B) break;
}
A.len+=A.a[A.len]>0;
return;
}
il bigint l2bigr(ll B){
static bigint ret; ret=bigint();
for(;;++ret.len){
ret.a[ret.len]=B%M,B/=M;
if(!B) break;
}
ret.len+=ret.a[ret.len]>0;
return ret;
}
il void s2big(bigint &A,const string &s){
A=bigint();
for(int i=s.size()-1,j;;i=j-1,++A.len){
j=mymax(0,i-8);
For(l,j,i) A.a[A.len]=(A.a[A.len]<<3)+(A.a[A.len]<<1)+(s[l]^48);
if(!j) break;
}
A.len+=A.a[A.len]>0;
return;
}
il bigint s2bigr(const string &s){
static bigint ret; ret=bigint();
static string in; cin>>in;
for(int i=s.size()-1,j;;i=j-1,++ret.len){
j=mymax(0,i-8);
For(l,j,i) ret.a[ret.len]=(ret.a[ret.len]<<3)+(ret.a[ret.len]<<1)+(s[l]^48);
if(!j) break;
}
ret.len+=ret.a[ret.len]>0;
return ret;
}
il void wt9(ll x){
static char wtst[10];
static int wttp=0;
while(x>9) wtst[++wttp]=x%10|48,x/=10;
wtst[++wttp]=x|48;
while(wttp<9) wtst[++wttp]='0';
while(wttp) pc(wtst[wttp--]);
return;
}
il void big9wt(const bigint &x){
static bool flag; flag=0;
for(int i=x.len-1;~i;--i)
if(flag) wt9(x.a[i]);
else if(x.a[i]) flag=1,wt(x.a[i]);
if(!flag) pc('0'); return;
}
il void big9wtk(const bigint &x){big9wt(x),pc(' '); return;}
il void big9wth(const bigint &x){big9wt(x),pc('\n'); return;}
il bool operator==(const bigint &A,const bigint &B){
if(A.len!=B.len) return false;
for(int i=A.len-1;~i;--i)
if(A.a[i]!=B.a[i])
return false;
return true;
}
il bool operator!=(const bigint &A,const bigint &B){
if(A.len!=B.len) return true;
for(int i=A.len-1;~i;--i)
if(A.a[i]!=B.a[i])
return true;
return false;
}
il bool operator<(const bigint &A,const bigint &B){
if(A.len!=B.len) return A.len<B.len;
for(int i=A.len-1;~i;--i)
if(A.a[i]!=B.a[i])
return A.a[i]<B.a[i];
return false;
}
il bool operator>(const bigint &A,const bigint &B){
if(A.len!=B.len) return A.len>B.len;
for(int i=A.len-1;~i;--i)
if(A.a[i]!=B.a[i])
return A.a[i]>B.a[i];
return false;
}
il bool operator<=(const bigint &A,const bigint &B){
if(A.len<B.len) return true;
if(A.len>B.len) return false;
for(int i=A.len-1;~i;--i)
if(A.a[i]!=B.a[i])
return A.a[i]<B.a[i];
return true;
}
il bool operator>=(const bigint &A,const bigint &B){
if(A.len<B.len) return false;
if(A.len>B.len) return true;
for(int i=A.len-1;~i;--i)
if(A.a[i]!=B.a[i])
return A.a[i]>B.a[i];
return true;
}
il void operator<<=(bigint &A,const int B){
if(B>=uselen){A=bigint(); return;}
static int mvlen; mvlen=mymin(uselen-B,A.len);
memmove(A.a+B,A.a,sizeof(ll)*mvlen);
memset(A.a,0,sizeof(ll)*mvlen);
A.len=mvlen+B; return;
}
il bigint operator<<(const bigint &A,const int B){
static bigint ret; ret=bigint();
if(B>=uselen) return ret;
static int mvlen; mvlen=mymin(uselen-B,A.len);
memcpy(ret.a+B,A.a,sizeof(ll)*mvlen);
ret.len=mvlen+B; return ret;
}
il void operator>>=(bigint &A,const int B){
if(B>=uselen){A=bigint(); return;}
static int mvlen; mvlen=A.len-B;
memmove(A.a,A.a+B,sizeof(ll)*mvlen);
memset(A.a+B,0,sizeof(ll)*mvlen);
A.len=mvlen; return;
}
il bigint operator>>(const bigint &A,const int B){
static bigint ret; ret=bigint();
if(B>=uselen) return ret;
static int mvlen; mvlen=A.len-B;
memcpy(ret.a,A.a+B,sizeof(ll)*mvlen);
ret.len=mvlen; return ret;
}
il void operator+=(bigint &A,ll B){
if(A.empty()){l2big(A,B); return;}
static int i;
for(i=0;B;++i){
B=B+A.a[i];
A.a[i]=B%M,B/=M;
}
chkmax(A.len,i);
return;
}
il bigint operator+(const bigint &A,ll B){
if(A.empty()) return l2bigr(B);
static bigint ret; ret=A;
static int i;
for(i=0;B;++i){
B=B+ret.a[i];
ret.a[i]=B%M,B/=M;
}
chkmax(ret.len,i);
return ret;
}
il void operator+=(bigint &A,const bigint &B){
if(A.empty()){A=B; return;}
static ll now; now=0,chkmax(A.len,B.len);
for(int i=0;i<A.len;++i){
now=now+A.a[i]+B.a[i];
if(now<M) A.a[i]=now,now=0;
else A.a[i]=now-M,now=1;
}
if(now) A.a[A.len]=now,++A.len;
return;
}
il bigint operator+(const bigint &A,const bigint &B){
if(A.empty()) return B;
if(B.empty()) return A;
static bigint ret; ret=bigint();
static ll now; now=0,ret.len=mymax(A.len,B.len);
for(int i=0;i<ret.len;++i){
now=now+A.a[i]+B.a[i];
if(now<M) ret.a[i]=now,now=0;
else ret.a[i]=now-M,now=1;
}
if(now) ret.a[ret.len]=now,++ret.len;
return ret;
}
il void operator-=(bigint &A,ll B){
static ll now;
for(int i=0;B;++i){
now=B-A.a[i];
if(now<=0) A.a[i]=-now,B=0;
else{
if(now%M) A.a[i]=M-now%M,B=now/M+1;
else A.a[i]=0,B=now/M;
}
}
while(A.len && !A.a[A.len-1]) --A.len;
return;
}
il bigint operator-(const bigint &A,ll B){
static bigint ret; ret=A;
static ll now;
for(int i=0;B;++i){
now=B-ret.a[i];
if(now<=0) ret.a[i]=-now,B=0;
else{
if(now%M) ret.a[i]=M-now%M,B=now/M+1;
else ret.a[i]=0,B=now/M;
}
}
while(ret.len && !ret.a[ret.len-1]) --ret.len;
return ret;
}
il void operator-=(bigint &A,const bigint &B){
static ll now; now=0;
for(int i=0;i<A.len;++i){
now=now+A.a[i]-B.a[i];
if(now>=0) A.a[i]=now,now=0;
else A.a[i]=now+M,now=-1;
}
while(A.len && !A.a[A.len-1]) --A.len;
return;
}
il bigint operator-(const bigint &A,const bigint &B){
static bigint ret; ret=bigint();
static ll now; now=0,ret.len=A.len;
for(int i=0;i<A.len;++i){
now=now+A.a[i]-B.a[i];
if(now>=0) ret.a[i]=now,now=0;
else ret.a[i]=now+M,now=-1;
}
while(ret.len && !ret.a[ret.len-1]) --ret.len;
return ret;
}
il void operator*=(bigint &A,const ll B){
static ll now; now=0;
for(int i=0;i<A.len;++i){
now=now+A.a[i]*B;
A.a[i]=now%M,now/=M;
}
while(now) A.a[A.len]=now%M,++A.len,now/=M;
return;
}
il bigint operator*(const bigint &A,const ll B){
static bigint ret; ret=bigint();
static ll now; now=0,ret.len=A.len;
for(int i=0;i<A.len;++i){
now=now+A.a[i]*B;
ret.a[i]=now%M;
now/=M;
}
while(now) ret.a[ret.len]=now%M,++ret.len,now/=M;
return ret;
}
il void operator*=(bigint &A,const bigint &B){
static bigint ret; ret=bigint();
static ll now; now=0,ret.len=mymin(A.len+B.len+1,uselen);
for(int i=0;i<A.len;++i){
for(int j=0;j<B.len;++j){
now+=ret.a[i+j]+A.a[i]*B.a[j];
ret.a[i+j]=now%M,now/=M;
}
for(int k=i+B.len;now;++k){
now+=ret.a[k];
ret.a[k]=now%M,now/=M;
}
}
while(ret.len && !ret.a[ret.len-1]) --ret.len;
A=ret; return;
}
il bigint operator*(const bigint &A,const bigint &B){
static bigint ret; ret=bigint();
static ll now; now=0,ret.len=mymin(A.len+B.len+1,uselen);
for(int i=0;i<A.len;++i){
for(int j=0;j<B.len;++j){
now+=ret.a[i+j]+A.a[i]*B.a[j];
ret.a[i+j]=now%M,now/=M;
}
for(int k=i+B.len;now;++k){
now+=ret.a[k];
ret.a[k]=now%M,now/=M;
}
}
while(ret.len && !ret.a[ret.len-1]) --ret.len;
return ret;
}
il void mul2(bigint &A){
static ll now; now=0;
for(int i=0;i<A.len;++i){
now=now+(A.a[i]<<1);
if(now<M) A.a[i]=now,now=0;
else A.a[i]=now-M,now=1;
}
if(now) A.a[A.len]=now,++A.len;
return;
}
il void operator/=(bigint &A,const ll B){
static ll now,res; now=0;
static bool flag; flag=1;
for(int i=A.len-1;~i;--i){
res=now+A.a[i];
A.a[i]=res/B;
if(flag){
if(!A.a[i]) --A.len;
else flag=0;
}
now=res%B*M;
}
return;
}
il pair<bigint,ll> operator/(const bigint &A,const ll B){
static pair<bigint,ll> ret;
static ll res;;
static bool flag; flag=1;
ret.fir=bigint(),ret.sec=0;
for(int i=A.len-1;~i;--i){
res=ret.sec+A.a[i];
ret.fir.a[i]=res/B;
if(flag && ret.fir.a[i]) ret.fir.len=i+1,flag=0;
ret.sec=res%B*M;
}
return ret;
}
il void div2(bigint &A){
static ll now,res; now=0;
static bool flag; flag=1;
for(int i=A.len-1;~i;--i){
res=now+A.a[i];
A.a[i]=res>>1;
if(flag){
if(!A.a[i]) --A.len;
else flag=0;
}
now=(res&1)*M;
}
return;
}
il void div2(pair<bigint,ll> &A){
static ll res; A.sec=0;
static bool flag; flag=1;
for(int i=A.fir.len-1;~i;--i){
res=A.sec+A.fir.a[i];
A.fir.a[i]=res>>1;
if(flag){
if(!A.fir.a[i]) --A.fir.len;
else flag=0;
}
A.sec=(res&1)*M;
}
A.sec/=M; return;
}
il pair<bigint,ll> div2r(const bigint &A){
static pair<bigint,ll> ret;
static ll res;
static bool flag; flag=0;
ret.fir=bigint(),ret.sec=0;
for(int i=A.len-1;~i;--i){
res=ret.sec+A.a[i];
ret.fir.a[i]=res>>1;
if(flag && ret.fir.a[i]) ret.fir.len=i+1,flag=0;
ret.sec=(res&1)*M;
}
ret.sec/=M; return ret;
}
struct big2{
string a;
big2(){a.clear();}
il char &operator[](int key){return a[key];}
il void push_back(int x){a.p_b(x); return;}
il size_t size(){return a.size();}
};
il big2 big2big2(const bigint &A){
static big2 ret; ret=big2();
static pair<bigint,ll> now; now.fir=A;
while(!now.fir.empty()) div2(now),ret.p_b(now.sec);
return ret;
}
il void big2wt(const big2 &A){
for(char c:A.a) pc(c|48);
return;
}
il void big2wtk(const big2 &A){big2wt(A),pc(' '); return;}
il void big2wth(const big2 &A){big2wt(A),pc('\n'); return;}
}
-
\(M\) 为进制,\(uselen\) 为最大位数(实际上用的是第 \(0\sim uselen-1\) 位)。足够表示 \(10^{9uselen}-1\) 的数字。
-
其中的读入和输出是基于 \(M=10^9\) 而实现的,若需要改变进制,请调整相关函数。
-
其中的左右移为 \(M\) 进制意义下的。特别实现了一个取以十为底的对数的操作,注意这一操作取到的对数是对数的一个不紧的下界,实际的对数 \(\lg A\) 的区间为 \([A.lg,A.lg+\lg 2)\)。
-
从 div2 开始是一车乱七八糟的二进制特化,以及一个不成熟的 string 式二进制高精。
-
因为设计上是打算直接
using namespace
的,所以命名空间的名称很长。