1.在相同deep的节点上二分时间戳
用vector存储相同deep的节点,做一个链表,然后通过at(i),函数二分就好了
引申1:可以在dfs序上找到一个节点的子树的时间戳的范围,然后在相同deep深度的链表上二分。那么就能得到关于一个节点的子树相同深度节点。时间O(log n),预处理O(n)。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int m,ans;
vector<int> a;
vector <int> :: iterator it;
int main()
{
for(int i=1;i<=101;i+=7) a.push_back(i);
for(it=a.begin();it!=a.end();it++) printf("%d ",*it);
scanf("%d",&m);
int l=0,r=a.size(),mid;
while(l<r)
{
mid=l+r>>1;
if(a.at(mid)<=m) ans=a.at(mid),l=mid+1;
else r=mid;
}
printf("%d",ans);
return 0;
}
其实可以直接将bfs序放到一个连续数组里,记录端点然后开始二分。
2.<cmath>之神器 log (double )
首先这个函数返回的是double。然后呢?又有什么卵用???
比如,给你一坨正整数,让你有一定的规则的瞎乘,找到乘积最大的取模输出。
但是,一堆数乘起来会爆啊,取模也会在比较的时候炸掉啊。
嗯,这是就需要log()函数了。
我们知道那个log(a*b)=log(a)+log(b)。另外log函数还是递增的,所以,直接加和,和大的就是乘积大的。
不过答案呢?我们并不能计算一个e^double,所以只能记录最优解的计算路径,然后再累乘取模。
3.zkw线段树
其实挺简单的(不过后面50多页的差分树我根本不会啊QAQ,哭死)。不过还是能做挺多东西的。
思想:类似手写heap,找到点编号的关系,建立一颗树,自底向上的搞,实现非递归。
下面为无区间修改的zkw
建树:
就是开够一个底层空间为n+2的树,将元素扔进底层,再从底向上更新参数。
底层第一个元素的编号为M,底层的位置个数为M,根为1(不过zkw都是自底向上,跟根毛关系都没有)
void build()
{
for(M=1;M<=n+1;M<<=1);//至少有n+2个位置,因为后面会有开区间查询,修改
for(int i=M+1;i<=n+M;i++) sum[i]=mx[i]=mi[i]=read(),size[i]=1;//一定要空一位,为后面维护极值的开区间做准备
for(int i=M-1;i;i--) pushup(i);//还是从M-1开始,因为M位没有值。
}
传递(pushup):(因题而异)
mx[pos]=max(mx[pos<<1],mx[pos<<1|1]);
mi[pos]=min(mi[pos<<1],mi[pos<<1|1]);
sum[pos]=sum[pos<<1]+sum[pos<<1|1];
size[pos]=size[pos<<1]+size[pos<<1|1];
//其实还有很多,应需搞一搞
查询:传进来的l,r是原数列的编号。也就是要求输出原数列[l,r]的解,写法都类似。
int query_mx(int l,int r)//闭区间
{
int re=-1<<30;
for(l=l+M-1,r=r+M+1;l^r^1;l>>=1,r>>=1)//变成开区间
{
if(~l&1) re=max(re,mx[l^1]);
if(r&1) re=max(re,mx[r^1]);
}
return re;
}
int query_mi(int l,int r)
{
int re=1<<30;
for(l=l+M-1,r=r+M+1;l^r^1;l>>=1,r>>=1)
{
if(~l&1) re=min(re,mi[l^1]);
if(r&1) re=min(re,mi[r^1]);
}
return re;
}
int query_sum(int l,int r)
{
int re=0;
for(l=l+M-1,r=r+M+1;l^r^1;l>>=1,r>>=1)
{
if(~l&1) re+=sum[l^1];
if(r&1) re+=sum[r^1];
}
return re;
}
单点修改:
void fix_mx(int id,int now)//把第id个数改成now
{
for(mx[id+=M]=now,id>>=1;id;id>>=1) push_mx_up(id);
}
void fix_mi(int id,int now)
{
for(mi[id+=M]=now,id>>=1;id;id>>=1) push_mi_up(id);
}
void fix_sum(int id,int now)
{
for(sum[id+=M]=now,id>>=1;id;id>>=1) push_sum_up(id);
}
单点查询和区间没啥不同的吧???变简单了好吧。
下面为带有区间修改的zkw
额...lazy标记是噩梦啊(线段树的精髓啊)...下面就给个区间修改,区间查询和的好吧。
建树:一样,只是多来了个lazy[]
传递:一样,(仅限于加和)
修改:噩梦开始。。。
由于要修改小区间,然而包含小区间的大区间的参数也会变,所以我们需要自底向上一路更新参数。还有就是,由于标记永久化 ,所以原来的标记可能并未下传到小区间,向上更新的时候需要累计当前节点代表区间的lazy值,所以还需要维护一个siz值。
void Fix(LL l,LL r,LL w)
{
for(l=l+M-1,r=r+M+1;l^r^1;l>>=1,r>>=1)
{
if(~l&1) lazy[l^1]+=w,t[l^1]+=siz[l^1]*w;//将节点直接处理好,查询的时候就不用再累加当前的lazy了
if(r&1) lazy[r^1]+=w,t[r^1]+=siz[r^1]*w;
t[l>>1]=t[l]+t[l^1]+siz[l>>1]*lazy[l>>1];t[r>>1]=t[r]+t[r^1]+siz[r>>1]*lazy[r>>1];//计算lazy值,一定要写
}
while(l>>=1) t[l]=t[l<<1]+t[l<<1|1]+lazy[l]*siz[l];
//由于内部是提前更新,所以while(l>>=1)先右移,后更新
}
查询:区间和
LL query(LL l,LL r)
{
if(r<l) return 0ll;
LL re=0,lsz=0,rsz=0;//lsz表示从左边上来的节点个数,用于累加lazy的值,rsz同理
for(l=l+M-1,r=r+M+1;l^r^1;l>>=1,r>>=1)
{
re+=(lazy[l]*lsz+lazy[r]*rsz);//累加下面小区间没有更新的部分
if(~l&1) re+=t[l^1],lsz+=siz[l^1];//由于t[]的值已经是更新好的了,所以不需要累加当前区间的lazy[]
if(r&1) re+=t[r^1],rsz+=siz[r^1];//需要累计的节点个数
}
re+=(lazy[l]*lsz+lazy[r]*rsz);//两边节点有共同父亲,lazy不同
lsz+=rsz;//siz累加
while(l>>=1) re+=lazy[l]*lsz;//继续向上,while(l>>=1) 先右移,后计算
return re;
}
看着zkw还挺简单的,但是需要考虑的还挺多的。需要拍熟。到考场上如果写错一个地方就GG了。