这周的状态不是很好,确实是偷懒了,开始两天把数据结构做了个结尾,把 treap 的模板终于调了出来,之后收到了开数学的通知,一开始我是挺愿意学着一部分的,我本身也对数学比较感兴趣(虽然啥也不会,只是感兴趣),同时数据结构的码量太大了,一个错误找半天,但是看了看数学的题单,数论学的一瓶子不满半瓶子晃荡的我看到题单上的基础知识几乎没有会的呀,而且还没有趁手的资料,虽然这周说是学了很久,但是我记得从早上8点开始准备学,一直到10点才决定从这个题码起,也就这两天,开始重温数论,果然他没让我失望,还是那么难……
先贴一下 Treap 的代码,二分查找树就是因为有旋转操作所以可以将时间复杂度降到 logn 的等级,还有旋转深度更大的节点的 splay 操作,在这里就先不介绍了
struct Node //树内各节点的属性
{
int l,r;
int fa,prl;
int val; //父节点,优先级,值
int sz,cnt; //节点的大小,节点包含重复元素的个数
}node[N];
int root=0,tot=0,ans; //根的标号,节点总个数,记录前驱后继答案
#define lson node[id].l
#define rson node[id].r
void update(const int id)
{
node[id].sz=node[lson].sz+node[rson].sz+node[id].cnt;
}
int new_node(int x)
{
node[++tot].val=x;
node[tot].prl=rand();
node[tot].sz=node[tot].cnt=1;
return tot;
}
void zig(int &x) //右旋
{
int y=node[x].l;
node[x].l=node[y].r,node[y].r=x;
node[y].sz=node[x].sz;
update(x);
x=y;
}
void zag(int &x) //左旋
{
int y=node[x].r;
node[x].r=node[y].l;node[y].l=x;
node[y].sz=node[x].sz;
update(x);
x=y;
}
void insert(int &id,const ll w) //添加一个节点
{
if(!id){
id=new_node(w);
return ;
}
node[id].sz++;
if(node[id].val==w) node[id].cnt++;
else if(node[id].val>w){
insert(lson,w);
if(node[id].prl>node[lson].prl) zig(id);
} else{
insert(rson,w);
if(node[id].prl>node[rson].prl) zag(id);
}
}
void erase(int &id,int x)//删除一个值为 x 的节点
{
if(id==0) return ;
if(node[id].val==x){
if(node[id].cnt>1){
node[id].cnt--;
node[id].sz--;
return ;
}
if(lson*rson==0) id=lson+rson;
else{
if(node[lson].prl<node[rson].prl) zig(id),erase(id,x);
else zag(id),erase(id,x);
}
}
else if(x>node[id].val){
node[id].sz--;
erase(rson,x);
}
else{
node[id].sz--;
erase(lson,x);
}
}
void query_pre(int id,const int x) //求比 w 小的最大数(前驱)
{
if(id==0) return ;
if(node[id].val<x){
ans=node[id].val;
query_pre(rson,x);
}else query_pre(lson,x);
}
void query_suf(int id,const int x) //求比 w 大的最小数(后继)
{
if(id==0) return ;
if(node[id].val>x){
ans=node[id].val;
query_suf(lson,x);
} else query_suf(rson,x);
}
int query_k(int id,int k) //寻找排名为第 k 大元素
{
if(id==0) return 0;
if(k<=node[lson].sz) return query_k(lson,k);
else if(k>node[lson].sz+node[id].cnt) return query_k(rson,k-node[lson].sz-node[id].cnt);
else return node[id].val;
}
int query_rand(int id,int x) //有多少数的值小于 x
{
if(id==0) return 0;
if(node[id].val==x) return node[lson].sz+1;
else if(x>node[id].val) return node[lson].sz+node[id].cnt+query_rand(rson,x);
else query_rand(lson,x);
}
在记录一个原理:鸽巢原理
鸽巢原理:有 n+1 只鸽子,n 个巢穴,一定有一个巢穴会有 >=2 只鸽子
现有一个题目:有 n 个数,现在可以任意挑选 m 个数,要求所选的 m 个数之和是 n 的倍数.
求初始数组前缀和对 n 取余,这样前缀和有 n 个数,根据鸽巢原理, 前缀和数组 sum[] 下标范围从 [0,n],因为 sum[0]=0 本身就是一个可以被 n 整除的数。
这样一定有两个前缀和 sum[i] ,sum[j] 相等,这样这 m 个数就是 a[i+1,j] , 这 m 个数恰好可以被 n 整除
init();
/*
void init()
{
ms(sum,0); //memset
ms(pos,-1);
sum[0]=0;
pos[0]=0;
}
*/
int flag=0;
for(i=1;i<=n;i++) sum[i]=(sum[i-1]+a[i])%m;
for(i=1;i<=n;i++){
if(pos[sum[i]]==-1) pos[sum[i]]=i;
else{
for(j=pos[sum[i]]+1;j<=i;j++){
if(flag) printf(" %d",j);
else printf("%d",j),flag=1;
}
break;
}
}
这周其实做数学这个专题,学习到的东西并不如数据结构学的那么充实,像鸽巢原理,扩展欧几里得原理,中国剩余定理等等这些,并不只是依靠模板可以掌握的,zqk 推荐 数论 的那本书刚刚把我教会。
像利用扩展欧几里得求逆元的方式,为什么不互质与互质的时候所用的方法不同要理解透彻。所用的这个函数到底是干什么用的,把这个函数打出来并没有什么,关键是他们能够干什么。
这周由于个人原因确实没有给到 acm 那么多的时间,学习的内容也没有过去几周看起来充实,但是数学这个专题刷题量是一方面,思考公式解决办法显得比其他的更重要。