1. 程式人生 > 其它 >基本漏洞原理及防禦(10)-DOS:CC攻擊

基本漏洞原理及防禦(10)-DOS:CC攻擊

Splay是一種很好地維護一棵二叉搜尋樹的方法。
如果不知道什麼是二叉搜尋樹,請看此頁面

基本思想

Splay的基本思想是,通過一系列的旋轉操作,維持整棵二叉搜尋樹的平衡。

旋轉

對於一個節點,我們可以對它進行旋轉。

左旋與右旋

對於這樣一個圖:

其中x節點有兩個子樹,A與B;x節點的父親y節點還有一棵子樹C;z節點是y節點的父親。
我們將x節點旋轉(這裡x節點是其父親y節點的左兒子,所以我們會將其右旋),結果是這個樣子的:

當然,如果我們把x旋轉回去的話,那就是左旋操作了。

總體來看是這個樣子:

在進行旋轉操作時,我們需要保持其中序遍歷序列不變。

在剛剛的右旋操作中,我們來分析一下我們需要改變的邊:

[splay 4]

就是這三條標紅的邊。

那麼對於這三條邊,我們分別進行重構操作。

程式碼如下:

int k = tr[y].s[1] == x;//x是y的哪個兒子
tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;//重構z-x邊
tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;//重構y-B邊
tr[x].s[k ^ 1] = y, tr[y].p = x;//重構x-y邊

核心函式

我們在向splay中插入一個數之後,會強制將其旋轉到根。
而在剛才的示例中,我們看到了,我們將x右旋之後,x就向上走了一點。
而經過不斷的旋轉,我們就可以讓x節點走到根。

當然,我們也不是隨便瞎轉,因為旋轉操作也是需要複雜度的。
而我們的最後目標是達到儘量小的複雜度。
引用閆學燦的一句話:

“如果我們瞎轉的話,就達不到\(O(\log n)\)的複雜度了。”
所以我們要根據x所處的位置來制定不同的旋轉方案。

首先,我們對於x可能出現的幾種情況分析一下:

  1. x就是目標節點。
    那麼就不用轉了。

  2. x是目標節點的子節點。
    那我們直接轉一下x就可以了。

對於x的父親也不是目標節點的情況,我們也分兩種情況討論。

  1. x的父親也不是目標節點,且x與其父親的所在子樹型別相同。
    可以理解為x,x的父親和x的父親的父親三個節點在一條直線上。
    這樣的話,我們就先旋轉x的父節點,再旋轉x。

3.x的父親也不是目標節點,且x與其父親的所在子樹型別不同。
可以理解為x,x的父親和x的父親的父親三個節點的連線是一條折線。
這樣的話,我們旋轉兩次x。

這樣不斷判斷,直到x到達目標節點。

基本操作

插入

int insert(int v)
{
	int u = root, p = 0;
	while(u) p = u, u = tr[u].s[v > tr[u].v];//如果節點非空,就一直向下跳對應的子樹
	u = ++idx;//開一個新的節點
	if(p) tr[p].s[v > tr[p].v] = u;//如果非根節點,那麼就更新一下子節點資訊
	tr[u].init(v, p);//初始化節點值
	splay(u, 0);//旋轉到根
	return u;
}

查詢第k個值

這裡查詢的是splay中序遍歷得到的序列中第k個值。

int get_k(int k)
{
	int u = root;
	while(true)
	{
		if(tr[tr[u].s[0]].size >= k)//在左子樹中
		{
			u = tr[u].s[0];
		}
		else if(tr[tr[u].s[0]].size + 1 == k)//就是這個點了!
		{
			return tr[u].v;
		}
		else//搜尋右子樹
		{
			k -= tr[tr[u].s[0]].size + 1;
			u = tr[u].s[1];
		}
	}
	return -1;
}

程式碼

給個洛谷板子題的程式碼:

它這裡面要求區間翻轉,那麼我們在進行每一次旋轉操作時,我們首先將左邊界的前驅旋轉至根節點,接著再把右邊界的後繼旋轉至根節點的下面,此時右邊界的後繼的左子樹就是我們所要翻轉的區間了。
我們順便增加一個flag標記,用來標記翻轉次數。

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
struct Node
{
    int s[2], f, v;
    int size, flag;
    void init(int _v, int _f)
    {
        v = _v, f = _f;
        size = 1;
    }
}tr[N];
int rt, idx;
void pushup(int x)
{
    tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + 1;
}
void pushdown(int x)
{
    if(tr[x].flag)
    {
        swap(tr[x].s[0], tr[x].s[1]);
        tr[tr[x].s[0]].flag ^= 1;
        tr[tr[x].s[1]].flag ^= 1;
        tr[x].flag = 0;
    }
}
void rotate(int x)
{
    int y = tr[x].f, z = tr[y].f;
    int k = tr[y].s[1] == x;
    tr[z].s[tr[z].s[1] == y] = x, tr[x].f = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].f = y;
    tr[x].s[k ^ 1] = y, tr[y].f = x;
    pushup(y), pushup(x);
}
void splay(int x, int k)
{
    while(tr[x].f != k)
    {
        int y = tr[x].f, z = tr[y].f;
        if(z != k)
        {
            if((tr[y].s[1] == x) ^ (tr[z].s[1] == y))
            {
                rotate(x);
            }
            else
            {
                rotate(y);
            }
        }
        rotate(x);
    }
    if(!k) rt = x;
}
void insert(int v)
{
    int u = rt, f = 0;
    while(u) f = u, u = tr[u].s[v > tr[u].v];
    u = ++idx;
    if(f) tr[f].s[v > tr[f].v] = u;
    tr[u].init(v, f);
    splay(u, 0);
}
int get_k(int k)
{
    int u = rt;
    while(true)
    {
        pushdown(u);
        if(tr[tr[u].s[0]].size >= k)
        {
            u = tr[u].s[0];
        }
        else if(tr[tr[u].s[0]].size + 1 == k)
        {
            return u;
        }
        else
        {
            k -= tr[tr[u].s[0]].size + 1;
            u = tr[u].s[1];
        }
    }
    return -1;
}
void output(int u)
{
    pushdown(u);
    if(tr[u].s[0]) output(tr[u].s[0]);
    if(tr[u].v >= 1 && tr[u].v <= n) printf("%d ", tr[u].v);
    if(tr[u].s[1]) output(tr[u].s[1]);
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 0; i <= n + 1; i++) insert(i);
    while(m--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        l = get_k(l), r = get_k(r + 2);
        splay(l, 0), splay(r, l);
        tr[tr[r].s[0]].flag ^= 1;
    }
    output(rt);
    return 0;
}