UOJ Logo tendency的博客

博客

图的表示

2018-09-11 00:09:03 By tendency

图就是一堆点和边,但是可以用不同的方式来存储和表示。

比如这一种

#include <vector>
#include <stdlib.h>
struct Edge {
  int v; 
  int w;
  Edge* next;
  Edge():v(-1), next(NULL){}
};


struct Graph {
  int V;
  int E;
  std::vector<Edge*> adj;
  std::vector<Edge> edge_pool;
  Edge* pool_tail;

  Graph(int v, int e): V(v), E(e) {
    adj.resize(V);
    edge_pool.resize(E);
    pool_tail = &edge_pool[0];
  }
  void add_edge(int u, int v, int w) {
    pool_tail->v = v;
    pool_tail->w = w;
    pool_tail->next = adj[u];
    adj[u] = pool_tail;
    ++pool_tail;
  }
};

/*
for (Edge* e = graph->adj[i];e;e = e->next)
*/

比如这一种

#include <vector>
struct Edge {
  int src;
  int dest;
  int weight;
};
struct Graph {
  int V;
  int E;
  std::vector<Edge> edge;
  Graph(int v, int e):V(v), E(e) {
    edge.resize(E);
  }
};

[图的最短路径算法]Dijkstra, Bellman-Ford, Floyd-Warshall

2018-09-11 00:05:24 By tendency

后缀数组

2018-09-05 11:41:28 By tendency

后缀数组模板题

后缀数组 suffix array是个什么玩意?

回答: 这是用来代替suffix tree的一个辅助的数据结构,按照字典序保存了一个字符串中的所有后缀。相比suffix tree, 据说实现起来更加简单,算法的常数因子小,且更容易充分利用cache locality,提高计算效率。

以题目中的$Str=ababa$为例,字符串$Str$的所有后缀为为

suffix i
ababa 1
baba 2
aba 3
ba 4
a 5

(uoj博客的表格是不是有问题=。=)

排序之后得到

suffix i
a 5
aba 3
ababa 1
ba 4
baba 2

所以最后我们要求的后缀数组就是 $[5,3,1,4,2]$

那么,如何才能得到suffix array呢?

最简单的就是整理出所有的后缀,然后排个序,这不是就搞定了?

然而算算复杂度。。。 排序的复杂度是 $O(nlogn)$,而每次比较字符串又需要花费 $O(n)$时间,所以总的复杂度是 $O(n^2 logn)$。后缀数组可是要应用到拥有天文数字长度的DNA检测中的,算法的复杂度显得至关重要。

幸运的是,计算机专家们已经找到了线性时间的算法,其中有DC3算法和KA算法,主要思想是利用每个后缀之间的关联,帮助进行后缀数组的排序。

更值得关注的是,目前后缀数组最快的、最实用的、(最适合OJ的)算法,SA-IS算法,是由我们中国人提出的,论文链接:农革 后缀数组。作者基于KA算法进行了改进,采用了“Almost Pure Induced-Sorting",就是说把原来KA算法中没有用 induced sort的部分也用这个诱导排序来实现,意外的发现效果惊人。

有时间贴个代码,并讲讲诱导排序。

所谓的诱导排序,就是利用一些威逼利诱的方法来进行排序,无需再重复的一次次扫面子字符串。 还是以 $Str=ababa$ 为例,首先我们在末尾添加一个无效的字符 \$ 当做结束标记,并且规定这个无效字符在排序上小于 $Str$ 中任何可能出现的字符。 原字符串为

1 2 3 4 5 6

a b a b a $

接下来,从右往左对标记每个字符的类型:如果 $Str[i]<Str[i+1]$,那么就标记字符为$S$类型($type[i]=S$);如果$Str[i]>Str[i+1]$,那么就标记字符为$L$类型($type[i]=L$);若两者相等,那么该字符的类型和右侧字符一样($type[i]=type[i+1]$)

1 2 3 4 5 6

a b a b a $

S L S L L S

那么,有什么好处呢?

算法认为, 如果 $type[i]== L$并且 $type[i+1]==S$,那么第 $i+1$ 个字符为可疑分子,先把他标个*号 (3 和 6)

1 2 3 4 5 6

a b a b a $

S L S L L S

      *       *

此外,算法会建三个桶(bucket),这是因为字符串中只出现了 $a$和 $b$,以及 \$, 各自建一个桶。目前有一点很清楚,后缀数组中以 $a$ 开头的子串肯定是排在以 $b$ 开头的子串前面。 而 \\$ 符号最小,放在最前面。这样6的位置的是确定的。 至于3, 先把他放到 $a$ 桶的最后。

{\$}{   a   }{ b }

{6}{x x 3}{x x}

{6}{5 x 3}{x x}

{6}{5 x 3}{4 x}

{6}{5 x 3}{4 2}

{6}{5 x 1}{4 2}

{6}{5 3 1}{4 2}

上面接下来几列就是诱导的过程。从左到右:6诱导5,5诱导4,3诱导2, 诱导出的位置放在桶内剩余的最靠左的位置。 到达右端之后再从右往左进行诱导, 2诱导1,4诱导3, 诱导出的位置放在桶内剩余的最靠右的位置。这样整个得到一个初步的后缀数组。

目前的后缀数组已经是正确的了。但是其实面对复杂的字符串,还需要在确定*字符后再继续重复如上的过程。有时候还需要递归。这里就不赘述了。见代码即可。

io库

2018-09-02 22:59:59 By tendency

做oj的时候,开始后我总发现自己的代码为什么比大神们慢这么多。

后来发现因为你用了cincout!!讲真,这个iostream库就是渣渣,性能有点问题,大型的项目都得自己写io库的。 scanfprintf是快很多,但还是比不上自己写的io库的啦~。

总而言之,不自己写io,你的代码在uoj上是无法跑到最快的。(很惭愧,本人就一次提交全站最快,说不定已经被人超了)

不废话了,我们来写写自己的io库,以后就可以复制粘贴啦:

namespace io {
const int MAXBUF = 1 << 20;
inline char get() {
    static char buf[MAXBUF],*p1,*p2;
    if(p1==p2) {
        p2 = (p1=buf)+fread(buf,1,MAXBUF,stdin);
        if (p1 == p2) return EOF;
    }
    return *p1++;
}

inline void read(int &x) {
   x = 0;
   bool is_neg = false;
    char ch =get();
    while(!(ch >='0' && ch <= '9') && ch != '-') ch = get();
   if (ch == '-') is_neg = true, ch = get();
    while((ch >='0' && ch <= '9')) x = x * 10 + ch  - '0',ch = get();
   if (is_neg) x = -x;
}

inline void read(float &x) {
   x = 0;
   bool is_neg = false;
    char ch =get();
   float basic = 1;
    while(!(ch >='0' && ch <= '9') && ch != '-') ch = get();
   if (ch == '-') is_neg = true, ch = get();
    while((ch >='0' && ch <= '9')) x = x * 10 + ch  - '0',ch = get();
   if (ch == '.') {
      ch = get();
   }
   while((ch >='0' && ch <= '9')) basic *= 0.1, x = x + basic * (ch  - '0'),ch = get();
   if (is_neg) x = -x;
}
}// namespacec io

自己的io库快的原因就是做了一些假设,不考虑各种错误情况(系统给的测试数据格式当然不会错啦),面对上百万的数据量自然省下不少运行时间。如果不检查是否为负数,那速度就更快啦。

NOI2014魔法森林题解 (link-cut tree)

2018-09-02 22:04:11 By tendency

总觉得这一题很经典,虽然说spfa算法也能搞定,但是学学link-cut tree挺好。

核心的思想就不多说了,这里大神很多。本人只是想改改代码,使得看起来更加容易一些,把c风格的天书代码转换为良好设计模式的c++风格代码。

这里先留一个目前看得懂的代码,以后上传改好的。

关于zig zag:

zig操作:

        y       x
      /          \ 
     /            \ 
   x       ->       y
    \              /
     z            z

zag操作:

     y                  x
      \                / 
       \              /
         x     ->   y
        /            \
       z              z

注意zig和zag是不改变树链的中序排列的。由于splay操作是一堆zig zag组合,所以splay操作也不会影响树链的中序,这里唯一的问题是reverse操作,会翻转树链中序。

为什么我们需要reverse?这是因为树链中序是有意义的,splay是辅助树,它的中序代表着原来的树中各个结点的实际的深度。 access操作会把该结点变成其所在树链中最深的一个点。所以最效率的表示方法就是把它提到所在splay树的根,然后删掉它的右子结点。一个根结点没有右子结点,这样看来它就是最右的结点啦,即原树链中最深的结点。

然而我们又需要makeroot操作,即把一个结点变为原树链的根结点。这可以借助刚才的access操作。只需要在access之后水平翻转整个splay树,这个根结点就从树链最深的结点变成了最浅的结点,即根结点。实际实现时,大神们发现可以给一个reverse标记即可,不用去当真翻转这个splay树。这个标记会在以后的splay操作中被处理(pushdown)。由于有时候标记会抵消,所以效率还是有提高的。而且这种lazy策略不会做不必要的操作。

总结来说,link cut tree的操作大致如下:

splay: 先做未完成的翻转,然后不断的zig zag辅助树。

access:即一个结点舍弃右儿子,然后不断认爹的过程。每个结点都有father指针,但是他的爹原来不认他,没把他当成左孩子或者右孩子。access不断找爹认,当他的右儿子。同时这个当爹的结点享受优惠政策,可以提到树链的根部,并且再帮他认爹,如此继续直到到达整个树的根部。最后一开始的这个结点再进行一个大型找爹之旅,因为现在形成了一整个大的树链,所以splay之后他成为了整个树的根。

makeroot: access + reverse

link:把这个结点当root,再给他安排个爹(虽然爹这时候还没有认这个儿子)

cut:access两个结点,然后断掉连接。

query: 一个结点当root,access另一个点,这样他们就在树链的两头,可以得到整个路径的信息。

#include <cstdio>
#include <algorithm>
#include <cstring>
#define M 100005
#define N 50005
#define nil T[0]
#define oo 1000000000
using namespace std;
int n, m;
namespace io {
const int MAXBUF = 1 << 20;
inline char get() {
    static char buf[MAXBUF],*p1,*p2;
    if(p1==p2) {
        p2 = (p1=buf)+fread(buf,1,MAXBUF,stdin);
        if (p1 == p2) return EOF;
    }
    return *p1++;
}

inline void read(int &x) {
   x = 0;
   bool is_neg = false;
    char ch =get();
    while(!(ch >='0' && ch <= '9') && ch != '-') ch = get();
   if (ch == '-') is_neg = true, ch = get();
    while((ch >='0' && ch <= '9')) x = x * 10 + ch  - '0',ch = get();
   if (is_neg) x = -x;
}

inline void read(float &x) {
   x = 0;
   bool is_neg = false;
    char ch =get();
   float basic = 1;
    while(!(ch >='0' && ch <= '9') && ch != '-') ch = get();
   if (ch == '-') is_neg = true, ch = get();
    while((ch >='0' && ch <= '9')) x = x * 10 + ch  - '0',ch = get();
   if (ch == '.') {
      ch = get();
   }
   while((ch >='0' && ch <= '9')) basic *= 0.1, x = x + basic * (ch  - '0'),ch = get();
   if (is_neg) x = -x;
}
}// namespacec io
struct node
{
    bool rev;
    int id, val;
    node *fa, *lc, *rc, *mx;
    node(bool rev = false, int id = 0, int val = 0, 
         node *fa = NULL, node *lc = NULL, node *rc = NULL, node *mx = NULL) :
        rev(rev), id(id), val(val), fa(fa), lc(lc), rc(rc), mx(mx) {}
    inline void update()
    {
        mx = this;
        if(mx -> val < lc -> mx -> val) mx = lc -> mx;
        if(mx -> val < rc -> mx -> val) mx = rc -> mx;
    }
    inline void pushdown()
    {
        if(rev)
        {
            lc -> rev ^= 1; rc -> rev ^= 1;
            swap(lc, rc);
            rev = false;
        }
    }
}*T[M + N], *S[M + N];
void zig(node *x)
{
    node *y = x -> fa;
    y -> lc = x -> rc;
    x -> rc -> fa = y;
    x -> rc = y;
    x -> fa = y -> fa;
    if(y == y -> fa -> lc) y -> fa -> lc = x;
    else if(y == y -> fa -> rc) y -> fa -> rc = x;
    y -> fa = x;
    y -> update();
}
void zag(node *x)
{
    node *y = x -> fa;
    y -> rc = x -> lc;
    x -> lc -> fa = y;
    x -> lc = y;
    x -> fa = y -> fa;
    if(y == y -> fa -> lc) y -> fa -> lc = x;
    else if(y == y -> fa -> rc) y -> fa -> rc = x;
    y -> fa = x;
    y -> update();
}
void splay(node *x)
{
    int top = 0;
    S[top++] = x;
    for(node *i = x; i == i -> fa -> lc || i == i -> fa -> rc; i = i -> fa)
    {
        S[top++] = i -> fa;
    }
    while(top--) S[top] -> pushdown();
    node *y = nil, *z = nil;
    while(x == x -> fa -> lc || x == x -> fa -> rc)
    {
        y = x -> fa; z = y -> fa;
        if(x == y -> lc)
        {
            if(y == z -> lc) zig(y);
            zig(x);
        }
        else
        {
            if(y == z -> rc) zag(y);
            zag(x);
        }
    }
    x -> update();
}
inline void access(node *x)
{
    node*t = x;
    for(node *y = nil; x != nil; y = x, x = x -> fa)
    {
        splay(x);
        x -> rc = y;
        x -> update();
    }
    splay(t);
}
inline void makeroot(node *x)
{
    access(x);  x -> rev ^= 1;
}
inline void lnk(node *x, node *y)
{
    makeroot(x);
    x -> fa = y;
}
inline void cut(node *x, node *y)
{
    makeroot(x);
    access(y); 
    x -> fa = y -> lc = nil;
    y -> update();
}
inline node *find(node *x)
{
    access(x);
    while(x -> lc != nil) x = x -> lc;
    return x;
}
inline node *query(node *x, node *y)
{
    makeroot(x);
    access(y); 
    return y -> mx;
}
struct edge
{
    int u, v, wa, wb;
    edge(int u = 0, int v = 0, int wa = 0, int wb = 0) :u(u), v(v), wa(wa), wb(wb) {}
    inline bool operator < (const edge &b) const
    {
        return wa < b.wa;
    }
}E[M];
int ufs[N];
inline int find(int x) { return x == ufs[x] ? x : ufs[x] = find(ufs[x]); }
int main()
{
#ifndef ONLINE_JUDGE
    freopen("d.txt", "r", stdin);
    freopen("a.txt", "w", stdout);
#endif
    scanf("%d%d", &n, &m);
    nil = new node();
    *nil = node(false, 0, 0, nil, nil, nil, nil);
    for(int i = 1; i <= n + m; ++i) T[i] = new node(false, i, 0, nil, nil, nil, nil);
    for(int i = 1; i <= n; ++i) ufs[i] = i;
    for(int i = 1; i <= m; ++i)
    {
        io::read(E[i].u);
        io::read(E[i].v);
        io::read(E[i].wa);
        io::read(E[i].wb);

    }
    sort(E + 1, E + m + 1);
    for(int i = 1; i <= m; ++i)
    {
        T[n + i] -> val = E[i].wb;
        T[n + i] -> mx = T[n + i];
    }
    int ans = oo;
    for(int i = 1; i <= m; ++i)
    {
        int x = find(E[i].u), y = find(E[i].v);
        if(x != y)
        {
            lnk(T[E[i].u], T[n + i]);
            lnk(T[E[i].v], T[n + i]);
            ufs[x] = y;
        }
        else
        {
            node *t = query(T[E[i].u], T[E[i].v]);
            if(t -> val > E[i].wb)
            {
                cut(T[E[t -> id - n].u], t);
                cut(T[E[t -> id - n].v], t);
                lnk(T[E[i].u], T[n + i]);
                lnk(T[E[i].v], T[n + i]);
            }
        }
        if(find(1) == find(n))
        {
            ans = min(ans, E[i].wa + query(T[1], T[n]) -> val);
        }
    }
    if(ans >= oo) puts("-1");
    else printf("%d\n", ans);
  //  for(int i = 0; i <= n + m; ++i)
 //   {
 //       delete T[i];
 //       T[i] = NULL;
 //   }

    return 0;
}
tendency Avatar