近期所学回顾

•--------流
•复习了最大流(bfs找增广路,dfs更新),个人爱好算法,ISAP(单个bfs进行多个dfs)
•最小费用最大流:spfa找是否存在到终点的最短路(一定会是增广路)

•算法实现:spfa+往回找

•核心板:
•ISAP:
•bool bfs()
•{
• queue<int >q;
• q.push(1);
• memset(dis,-1,sizeof(dis));
• dis[1]=0;
• while(!q.empty())
• {
• int u=q.front();
• q.pop();
• for(int i=head[u];i!=-1;i=e[i].next)
• {
• int v=e[i].v;
• if(dis[v]==-1&&e[i].cap>0)
• {
• dis[v]=dis[u]+1;
• if(v==n)return 1;
• q.push(v);
• }
• }
• }
• return 0;
•}
•int dfs(int u,int low)
•{
• if(u==n||low==0)return low;
• int ret=0;
• for(int i=head[u];i!=-1;i=e[i].next)
• {
• int v=e[i].v;
• if(dis[v]==dis[u]+1)
• {
• int f=dfs(v,min(low,e[i].cap));
• e[i].cap-=f;
• e[i^1].cap+=f;
• ret+=f;
• low-=f;
• if(low==0)return ret;
• }
• }
• dis[u]=-1;
• return ret;
•}
•void ISAP()
•{
• while(bfs())
• {
• int x;
• if(x=dfs(1,0x3f3f3f3f))maxflow+=x;
• }
•}

•最小费用最大流(例:方格取数2):
•bool spfa()
•{
• for(int i=S;i<=T;i++)
• {
• inq[i]=(i==S)?1:0;
• dis[i]=(i==S)?0:0x3f3f3f3f;
• fa[i]=0;
• }//初始化
• while(!q.empty())q.pop();//清空队列
• q.push(S);
• while(!q.empty())//spfa
• {
• int u=q.front();
• q.pop();
• inq[u]=0;
• for(int i=head[u];i!=-1;i=e[i].next)
• {
• int v=e[i].v;
• if(e[i].cap>0&&dis[v]>dis[u]+e[i].w)//松弛(多含了判断是否存在可行流)
• {
• dis[v]=dis[u]+e[i].w;
• fa[v]=i;//记录前驱边号
• if(!inq[v])
• {
• inq[v]=1;
• q.push(v);
• }
• }
• }
• }
• if(dis[T]==0x3f3f3f3f)return false;
• return true;//是否找到
•}

•void work()
•{
• int mincost=0;//最小花费
• while(spfa())//每次不断找最小费用可行流
• {
• int f=0x3f3f3f3f;
• for(int i=T;i!=S;i=e[fa[i]].u)
• {
• f=min(f,e[fa[i]].cap);
• }//回溯寻找这条路上的最小流量
• for(int i=T;i!=S;i=e[fa[i]].u)
• {
• e[fa[i]].cap-=f;
• e[fa[i]^1].cap+=f;
• }//流量变更
• mincost+=f*dis[T];//得到最大流  (整条路的最小费用*经过次数)  每花费1的流量跑一次路
• }
• mincost=-mincost;//因为存的是负数最小,要转变为正数最大
• printf("%d\n",mincost);
•}

•补充:
•关于流,我们都需要建反向弧。
•边号从0开始,这样把他的反向弧和它建在一起,操作的时候^1就行了~
•关于最小费用最大流:如果是保证点经过的次数,我们需要拆点~

•主席树:
•详见主席树流程。
•主席树以建新的节点来节省空间而得到效果,对于每次操作,我们需要log n的新点,复杂度就是n logn.
•操作流程回顾:去重->建树(返回新建点的边号)->updata()更新信息的时候先拷贝原来的值(需要新的编号)再更新->查询(有几种查询,如果需要和以前比较就逐层比较)。

•以静态第k小为例:
•(申请节点最好用静态的内存池)&S[++cnt].
•也可以用new node(),有些OJ会卡。
•low_bound().
•int build(int l,int r)
•{
• int rt=tot++;
• T[rt].size=0;
• if(l==r)return rt;
• int mid=(l+r)/2;
• T[rt].l=build(l,mid);
• T[rt].r=build(mid+1,r);
• return rt;
•}//建树

•int updata(int u,int l,int r,int x,int v)/*新建节点和u
•进行比较(同一级),在区间[l,r]中把x的位置的size加上v
•*/
•{
• int rt=tot++;
• T[rt]=T[u];//信息传递
• T[rt].size+=v;//加上size
• //要注意的是构造新节点的时候我们是从上往下构造的,
•    //而不是回溯的时候
• if(l==r)return rt;
• int mid=(l+r)/2;
• if(x<=mid)//要修改的信息在左儿子
• {
• T[rt].l=updata(T[u].l,l,mid,x,v);
• }
• else T[rt].r=updata(T[u].r,mid+1,r,x,v);
• return rt;
•}

•int query(int i,int j,int l,int r,int k)//i,j两棵树对比
•{
• if(l==r)return l;
• int cha=T[T[j].l].size-T[T[i].l].size;
• int mid=(l+r)/2;
• if(cha>=k)return query(T[i].l,T[j].l,l,mid,k);
• else return query(T[i].r,T[j].r,mid+1,r,k-cha);
•}

•最后要谈区间不下放:
•为了节省空间我们在lazy标记时不能层层下放,因为最坏可达到要建n个节点,这样的操作数是极大的。
•对于要查询的区间,新建一个点赋值属性并打上lazy,查询的时候,查询到每一层都加上当前点的lazy往下走,到底层累加lazy*区间长度 就得到了lazy所要更新的值了。
•附代码:

•更新:
•int updata(int u,int l,int r,int L,int R,int v)
•{
• int rt=tot++;
• T[rt]=T[u];
• if(l>=L&&r<=R)
• {
• T[rt].lazy+=v;
• return rt;
• }
• T[rt].sum+=(long long)((min(r,R)-max(l,L)+1)*v);
• int mid=(l+r)/2;
• if(L<=mid)T[rt].l=updata(T[u].l,l,mid,L,R,v);
• if(R>mid)T[rt].r=updata(T[u].r,mid+1,r,L,R,v);
• return rt;
•}

•查询:
•long long query(int u,int l,int r,int L,int R,int lazy)
•{
• if(l>=L&&r<=R)
• {
• return T[u].sum+(r-l+1)*(lazy+T[u].lazy);
• }
• int mid=(l+r)/2;
• long long  ans=0;
• if(L<=mid)ans+=query(T[u].l,l,mid,L,R,lazy+T[u].lazy);
• if(R>mid)ans+=query(T[u].r,mid+1,r,L,R,lazy+T[u].lazy);
• return ans;
•}

•Splay可以证明是nlogn的复杂度,具体为什么这个我不知道。
•我是靠 “营业额”入门的。
•维护一颗平衡树的平衡性(左子树权值>当前点>右子树权值)。
•旋转:中序遍历恒定。
•旋转的性质给我们提供了帮助。
•左旋、右旋:当前点和它的父亲位置交换(不改变平衡性)。

•这一块就处理详细一点:
•首先是准备:
•每一个splay必不可少的就是rotate和splay
•其余的都是充当辅助工具的,掌握了旋转和splay,再粗略了解其它怎么写了之后就问题不大了~
•struct node
•{
• node *f;
• node *ch[2];
• int v;
• node()
• {
• f=ch[0]=ch[1]=NULL;
• v=0;
• }
•}S[maxn];
•node *root;

•左右旋(合并在一起,原理请转向 splay 入门):
•(指针版本)
•void rotate(node *u)
•{
• node *f=u->f;
• if(f==NULL)return ;
• int d=u==f->ch[1];
• node *ff=f->f;
• int dd=0;
• if(ff!=NULL)dd=f==ff->ch[1];//bug
• f->ch[d]=u->ch[d^1];
• if(u->ch[d^1]!=NULL)u->ch[d^1]->f=f;
• u->ch[d^1]=f;
• f->f=u;
• u->f=ff;
• if(ff!=NULL)ff->ch[dd]=u;
•}

•void splay(node *u,node *p)//把u旋转到p的下面
•{
• while(u->f!=p)
• {
• node *f=u->f;
• node *ff=f->f;
• if(ff==p)
• {
• rotate(u);
• break;
• }
• int d=u==f->ch[1];
• int dd=f==ff->ch[1];
• if(d==dd)rotate(f);
• else rotate(u);
• rotate(u);
• }
• if(p==NULL)root=u;
•}
t:216;mso-char-wrap:1;mso-kinsoku-overflow:1'>    
•void insert(int key)
•{
• if(root==NULL)//空树
• {
• root=&S[++ncnt];
• root->v=key;
• return ;
• }
• node *x=root;
• node *y;
• while(1)
• {
• //分向左找和向右找
• if(key<x->v)
• {
• if(x->ch[0])x=x->ch[0];
• else//插入
• {
• y=&S[++ncnt];
• y->v=key;
• y->f=x;
• x->ch[0]=y;
• break;
• }
• }
• else
• {
• if(x->ch[1])x=x->ch[1];
• else
• {
• y=&S[++ncnt];
• y->v=key;
• y->f=x;
• x->ch[1]=y;
• break;
• }
• }
• }
• splay(y,NULL);
•}

•主要操作掌握了之后一切都变得很简单。
•掌握方法,理解splay原理,之后一切都可以通过联系。
•推荐习题:
•营业额
•Poj3468(splay实现区间lazy线段树)
•(把l-1旋到根,r+1选到根的下面,根的右子树的左子树就是区间[l,r])
•Play with Chain
•Robotic Sort
•codevs1514书架
•codevs3303翻转区间
•codevs1743翻转卡片

•接下来是AC自动机.
•维护一棵字典树然后通过构建失配指针,然后进行查询。
•(听说是维护前缀)。
•入门例题hdu 2222
•不难想到,对于前面有某一相同前缀的串可以共用一些点。
•那么每次读入一个模式串,我们就把它插入这棵字典树。
•Struct node
•{
•  node *fail;//失配指针,等会会用到
•  node *next[26];//字母是26个,为了操作方便,我们对每个点建立26个子节点。
•  int cnt;//以这个点为结尾字母的单词的个数;
•/*有可能出现这种情况 abc,abcd 那么我们需要在c的位置cnt++,d的位置cnt++ */
•Node(){fail=NULL;cnt=0;for(int i=0;i<26;i++)next[i]=NULL}//新节点
•}
•Node *root;
•//之后调用new node 貌似不需要内存池了。
•然后建树

•插入代码,比较简单:
•void insert(char *s)
•{
• node *u=root;
• int len=strlen(s);
• int tmp;//
• for(int i=0;i<len;i++)
• {
• tmp=s[i]-'a';
• if(u->next[tmp]==NULL)u->next[tmp]=new node();
• u=u->next[tmp];
• }
• u->cnt++;
•}

•显然,只是插入这棵树,现在进行匹配也无从下手。
•暴力的话又会回到朴素算法。
•类似于kmp的,我们也需要构造next失配指针。
•我们利用bfs进行高效的构造。
•每次取出队首元素,把它的所有子节点扫一遍,扫的过程中对于每一个元素,我们从当前节点的失配指针开始往上找,直到找到有个点的next[i]和当前i相等,就把i和那个点的next[i]连一条边,即失配指针,然后把这个子节点压入队列。
•为什么这么做是对的,我提出我的理解(不一定是对的)
•对于每一层,我们总是从最左边的开始找,因为失配指针连接的两个点有公共前缀,也就保证了当前点u和它的子节点u->son[i]需要找到u的某个失配点p(p一定等于u),满足p->son[i]=u->son[i],那么u->son[i]的失配指针就是p->son[i].
•Bfs构造失配指针

•Void build_ac()
•{
•  queue<node *>q;
•  q.push(root);
•  whlie(!q.empty())//宽搜队列
• {
•   node *u=q.front();q.pop();
•   for(int i=0;i<26;i++)
•   {
•     node *son=u->next[i]; //对于每一个儿子
•     if(son!=NULL)    //如果为空就不管了
•     {
•       if(u==root)son->fail=root;//根的情况,特判
•      else{      
•        node *tmp=u->fail; //我们要匹配子节点的fail,子节点的父亲就是u;
•         while(tmp!=NULL)//不断找u的fail
•         {
•           if(tmp->next[i]!=NULL){son->fail=tmp->next[i];break;}//保证最先找到能匹配的点。
•           tmp=tmp->fail;
•         }
•        If(tmp==NULL)son->fail=root;//不能匹配,回到根
•      }
•       q.push(son);//每个节点进队一次出队一次
•     }
•   }
• }
•}

•处理之后,失配指针得到了,就下来就轻松了。
•令需要匹配的文本串为p
•那么 p[]  i∈[1,m]
•M=strlen(p);
•For(int i=1;i<=m;i++)
•对于当前位,我们需要知道它能否匹配。
•这样考虑,我们看当前点u的下一位,对于s[i]-’a’这一位存不存在,存在的话就可以往下走,不存在就跳到失配指针那里继续比较。
•于是我们会有两种结果:
•①可以匹配,往下走;
•②不能匹配,回到了root,重新来。
•同时,把这个能匹配到的点以及所有失配指针上的点的cnt加起来(cnt是以当前字母为结尾的单词个数),并且标记一下,下次就不在计算了。

•代码:
•Int query()
•{
•   node *u=root;node *tmp;
•   int len=strlen(s);
•   int ans=0;
•   for(int i=0;i<len;i++)
•   {
•     int id=s[i]-’a’;//需要匹配的下一位
•     while(u->next[id]==NULL&&u!=root)u=u->fail;//如果失配需要一直跳失配指针,最后悔出现两种可能,如上页
•     u=u->next[i];
•     if(u==NULL)u=root;//到根且失配,返回根
•     tmp=u;   //从u找所有失配指针(都是以u结尾,前缀相同)累加所有以它结尾的单词个数
•     while(tmp!=root&&tmp->cnt!=-1)//-1做标记,加了一次后不再加,且如果当前已经被计算,那么根据bfs求失配指针的性质可知后面的失配指针指向的位置一定也被计算了
•     {
•       ans+=tmp->cnt;
•       tmp->cnt=-1;
•       tmp=tmp->fail;
•     }
•  }
• return ans;
•}

•类似于kmp,ac自动机工作核心就是失配指针,整个程序的重点是如何构造失配指针,请务必先理解原理(至少要有大概印象,再去翻译理解代码)
•同时,我的入门题hdu 2222
•Or 非vjudge

•树链剖分,最近才学,然后疯狂刷题。
•(一套题还差一道)
•树链剖分,也比较简单。
•先用两个dfs修改出6个信息,分别是son(重儿子)、size(子树大小)、fa(父亲节点)、dep(当前点的深度)、top(当前链头)、w(对应在线段书里面的编号)。
•之后就没有什么了,套上线段树模板。

•习题集:
•Problem A Aragorn's Story  1 /  1
•Problem B Housewife Wind  1 /  1
•Problem C Tree  1 /  1
•Problem D 染色  1 /  1
•Problem E 树的统计 Count  3 /  5
•Problem F 过路费  1 /  1
•Problem G Aladdin and the Return Journey  1 /  1
•Problem H Query on a tree
•Problem I Grass Planting


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值