一:文件读入输出
freopen("Add.in","r",stdin);
freopen("Add.out","w",stdout);
就是类似于这种,比如一道题目的文件名为Add,那么就可以通过这样的格式来完成读入以及输出.
二:读入输出挂
void Rd(int &res){
res=0;char p;
while(p=getchar(),p<'0');
do{
res=(res<<1)+(res<<3)+(p^48);
}while(p=getchar(),p>='0');
}
void Pt(int x){
if(x==0)return;
Pt(x/10);
putchar(x%10^48);
}
void Ps(int x){
if(x<0)putchar('-'),x=-x;
if(x==0)putchar('0');
else Pt(x);
}
就是逐个字符地读入数据,从而让读入更加快速.
输出挂的原理也是一样的,都是通过将输出数字变成输出字符以加快速度(都是黑科技).
如果要读入负数,要在读入挂中加入一些东西.
void Rd(int &res){
res=0;char p;int k=1;
while(p=getchar(),!(p>='0'&&p<='9')&&p!='-');
if(p=='-')k=-1,p=getchar();
do{
res=(res<<1)+(res<<3)+(p^48);
}while(p=getchar(),(p>='0'&&p<='9'));
res*=k;
}
就是在读入中也要判断一下是否已经读到了负号.
三:数据结构
数据结构是这门竞赛的很重要的一部分吧,很多东西的实现都要靠数据结构才能够得到很好的优化(就像在一些dp题中把 O(n2) 优化到 O(nlogn) ),从而完成很多题目.有些题目甚至是想到某些数据结构就能够直接写出来了.
1.并查集:
int fa[M];
void Init(){
//将所有元素的father指向自己
for(int i=1;i<=n;i++)
fa[i]=i;
}
int getfa(int x){
//找到某个元素当前的father,同时进行路径压缩
return x==fa[x]?x:fa[x]=getfa(fa[x]);
}
并查集的话,主要的东西就只有这一点了.
就是每个元素都会指向一个father,然后每个集合内的元素都会指向同一个元素.
然后合并两个集合的时候,就只需要找到这两个集合中的各一个元素,然后使用getfa函数找到他们两个集合的根,把其中的一个根指向另外一个根就好了.
并查集的话,可以快速地合并两个集合,以及判断两个集合是否连通,在一些题目中可以很有效地使用(比如当向一张图中加边时,可以用并查集来判断两个点之间有没有一条路径相连).
2.树状数组
struct Bit{
int Tree[M];
void Add(int x,int v){
while(x<=n){
Tree[x]+=v;
x+=x&(-x);
}
}
int Sum(int x){
int re=0;
while(x){
re+=Tree[x];
x-=x&(-x);
}
return re;
}
}T;
就是类似于这样的数据结构,能够支持在某一个地方加上一个数,然后支持询问前缀和.
x&(-x)
是得到x的二进制表示的最后一个1(也就是lowbit).
然后这样的原理就和上面的那一张图很像,就是每个询问会找到一些二进制位所对应的块,然后把这些块里的信息收集上来,就完成了一次询问.
然后更新的话,要把上面那张图的对应数字上的块都更新一下,就可以用x+=x&(-x)
来做到了.
然后如果只用维护1到n的关系的话,也可以用树状数组,比如要求1到n的最大值什么的.
3.线段树
struct SegmentTree{
int Tree[M<<2];
void build(int L,int R,int p){
//L,R是线段树这个节点的两个端点,p是这个节点的编号.
Tree[p]=0;
if(L==R)return;
int mid=L+R>>1;
build(L,mid,p<<1);
build(mid+1,R,p<<1|1);
}
void update(int L,int R,int p,int x,int v){
//把x这个点改成v
if(L==R){
Tree[x]=x;
return;
}
int mid=L+R>>1;
if(mid>=x)update(L,mid,p<<1,x,v);
else update(mid+1,R,p<<1|1,x,v);
Tree[p]=max(Tree[p<<1],Tree[p<<1|1]);
}
int query(int L,int R,int p,int l,int r){
//查询[l,r]的最大值
if(L==l&&R==r)return Tree[p];
int mid=L+R>>1;
if(mid>=r)return query(L,mid,p<<1,l,r);
else if(mid<l)return query(mid+1,R,p<<1|1,l,r);
else return max(query(L,mid,p<<1,l,mid),query(mid+1,R,p<<1|1,mid+1,r));
}
}T;
这样的话,就是一个支持单点更新,区间查询最大值的一个线段树了.
线段树其实是树状数组的升级版吧(我觉得),虽然常数什么的更大一些,但是能够做的事情更多,比如它可以只求一个区间内的极值,和之类的,而不是1到x的极值以及和.
所以说线段树能够做的事情更多,但是它用的空间也更大一些,但是无伤大雅啊.只要内存开的下就好了.
但是线段树的话还可以支持区间更新或者单点查询什么的,只要在代码里改一下就好了.
//区间更新,以将区间一段数加上一个相同的数为例吧,然后就是求区间的和.
void update(int L,int R,int p,int l,int r,int v){
if(L==l&&R==r){
Tree[p]+=v*(r-l+1);
Add[p]+=v;
return;
}
int mid=L+R>>1;
if(Add[p]!=0){
Add[p<<1]+=Add[p];
Add[p<<1|1]+=Add[p];
Tree[p<<1]+=Add[p]*(mid-l+1);
Tree[p<<1|1]+=Add[p]*(r-mid);
Add[p]=0;
}
if(mid>=r)update(L,mid,p<<1,l,r,v);
else if(mid<l)update(mid+1,R,p<<1|1,l,r,v);
else update(L,mid,p<<1,l,mid,v),update(mid+1,R,p<<1|1,mid+1,r,v);
Tree[p]=Tree[p<<1]+Tree[p<<1|1];
}
单点查询什么的还是不打了吧…
4.st表
struct StTable{
int val[M],Mx[LOGM][M],Log[M];
void Init(){
Log[0]=-1;
for(int i=1;i<M;i++)Log[i]=Log[i>>1]+1;
for(int i=1;i<=n;i++)Mx[0][i]=val[i];
for(int i=0;i+1<LOGM;i++)
for(int j=1;j<=n;j++)
if(j+(1<<i)<=n)Mx[i+1][j]=max(Mx[i][j],Mx[i][j+(1<<i)]);
else Mx[i+1][j]=Mx[i][j];
}
int Query(int l,int r){
int len=r-l+1;
int Lg=Log[len];
return max(Mx[Lg][l],Mx[Lg][r-(1<<Lg)+1]);
}
}St;
差不多就是这样的,就是通过 O(nlogn) 的预处理,然后做到 O(1) 地查询一个区间内的最小值.
就是和倍增一样的思想,先预先处理出每个数字向后再走 2n 个数的最大值,然后对于一个[l,r]的询问,我们先处理出一个k,使得 2k+1>=(r−l+1) ,然后对于两个大小为 2k 的区间,这样的区间就能够覆盖整个区间了,然后就可以做到 O(1) 的进行查询区间极值.
5.正向表
struct Li{
int to,co,nx;
}Lis[M];
int Head[M],tot=0;
void Add(int x,int y,int z){
//在x,y之间 连一条边权为z的边
Lis[tot].to=y,Lis[tot].co=z,Lis[tot].nx=Head[x],Head[x]=tot++;
Lis[tot].to=x,Lis[tot].co=z,Lis[tot].nx=Head[y],Head[y]=tot++;
}
就是开一个内存池,然后把所有边都储存在里面,因为如果用vector的话,因为要维护下标,所以在运行速度上会有一些偏差,这时候就能够用正向表来加快速度(骗分利器).
然后遍历的话就是这样的:
for(int i=Head[x];~i;i=Lis[i].nx)//遍历
6.高精度
struct Big{
#define P 10000
int num[1005],len;
Big(){
memset(num,0,sizeof(num));
len=1;
}
void Rd(){
scanf("%s",A);
int stlen=strlen(A);
len=0;
for(int i=stlen-1,res;res=0,i>=0;num[len++]=res,i-=4)
if(i>=3){
for(int j=i-3;j<=i;j++)res=(res<<1)+(res<<3)+(A[j]^48);
}else {
for(int j=0;j<=i;j++)res=(res<<1)+(res<<3)+(A[j]^48);
}
}
bool operator <(const Big &a)const{
if(len!=a.len)return len<a.len;
for(int i=len-1;i>=0;i--)
if