1. 程式人生 > >暑假清北學堂集訓筆記

暑假清北學堂集訓筆記

一個數 二維 clock 選擇 依次 父節點 壓縮 print dijk

day -1

訂票訂得晚只好坐淩晨1點的火車……

day 0

7點鐘到北京了,坐滴滴到酒店,然後去華北電力大學報道,路上看到一輛共享單車,弄了大半天才發現是壞的。。。

報完到就在旁邊的餐廳吃了起來。

day 1

南小鳥(鐘皓曦)講 搜索 分治 倍增 貪心

ST表: f[i][j]表示 從i到i+2^j-1這2^j個位置的元素最大值 初始化: f[i][0]=z[i] 轉移: f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1])

LCA(最近公公祖先):f[i][j] 表示從樹上編號為i的點向上走2^j步會走到哪個點 初始化: f[i][0]=father[i] 轉移: f[i][j]=f[f[i][j-1][j-1]

求a,b,LCA時將a置為深度深的那個點,然後一直往上走,知道兩個點深度相同,再將兩個點同時向上走,走到同一個點時,那個點就是他們的LCA

inline void dfs(int now,int f) {
    for (unsigned i = 0;i < edges[now].size();i++)
		if (!deep[edges[now][i]] && edges[now][i] != f) {
	                deep[edges[now][i]] = deep[now]+1;
			father[edges[now][i]][0] = now;
			dfs(edges[now][i],now);
	        }
}
void init() {  
    for (int j = 1;(1<<j) <= n;j++)
        for (int i = 1;i <= n;i++)
            if (father[i][j-1] != -1) father[i][j] = father[father[i][j-1]][j-1];
}  
inline int lca(int a,int b) {
    if (deep[a] < deep[b]) swap(a,b);
    int i;
    for (i = 0;(1<<i) <= deep[a];i++);
    i--;
    for (int j = i;j >= 0;j--)
        if (deep[a]-(1<<j) >= deep[b]) a = father[a][j];
    if (a == b) return a;
    for (int j = i;j >= 0;j--) {
        if (father[a][j] != -1 && father[a][j] != father[b][j]) {
            a = father[a][j];
            b = father[b][j];
        }
    }
    return father[a][0];
}    

快速冪:快速冪是倍增和分治的結合,如:2^50可以分為(2^25)^2 2^25又可以分為(2^12)^2*2一直這樣,我們就可以在O(log n)時間內求出x^n

inline int QuickPow(int x,int y) {
    if (y == 1) return x;
    int z = QuickPow(x,y>>1);
    if (y&1) return z*z*x%mod;
    return z*z%mod;
}

二分查找:二分查找可以在一個單調的序列中用O(log n)的時間找出一個數,它是一種分治

inline int find(int l,int r,int x) {
    if (r == l) return l;
    int mid = l+r>>1;
    if (x < a[mid]) return find(l,mid);
    return find(mid,r);
}

貪心:貪心一般形式是,以某種方式將所有物品排序,排序後按照從小到大進行選擇

搜索:dfs,bfs

搜索優化:

剪枝:把不優於當前最優解的狀態剪掉

卡時:卡時是一種騙分技巧,一般用於最優性問題,當程序準備超時時,直接輸出當前答案,然後結束程序

#include <cstdlib>
#include <cstdio>
#include <ctime>
int t,ans;
inline void dfs(...) {
    if (clock-t >= 990) {
        printf("%d",ans);
        exit(0);
    }
    ...
}
int main() {
    t = clock();
    ...
    dfs(...);
    printf("%d",ans);
    return 0;
}

晚上測試

第一題是一題不可做的模擬題!!!

我寫裏1.5h+

結果還是沒能調出來

第二題寫了一個四重循環加卡時

第三題沒看

結果

……

0+0+0=0

爆零啦!!!!!!!!!!!!! 有大佬200 Orz %%%

day 2

一上午都在講數論,作為五年級小學生表示聽不懂!!!!!!!!!!!!!!!!!!!

中午南小鳥走了,楊樂來了

楊樂講了一些背包和記憶化搜索

day 3

上午,楊樂又講了一些線性dp

LIS:最長不下降子序列

假設我們需要求以x結尾的最長下降子序列dp[x],由最憂性可得,我們除去最後一個位置(也就是x),還是一段最長下降,

那我們可以枚舉這個子序列的結尾y,最優值就是dp[y]。但需要註意的是,必須保證A[x] < A[Y], x比 Y要低,才滿足下降的要求。

我們從所有枚舉的結果中找到一個最大的即可。

轉移方程:dp[i] = max{dp[j]+1} (j<i a[i]<a[j])

LCS:最長公共子序列

假設我們需要求兩個序列分別以i,j結尾的最長公共子序列

dp[i][j], 接下來我們可以分幾種情況討論:

a[i]不在公共子序列中,那麽長度則等於dp[i-1][j]

b[j]不在公共子序列中,那麽長度則等於dp[i][j-1]

a[i]與b[j]都在子序列中,並且兩者匹配,那麽長度等於dp[i-1][j-1]+1

然後還講了區間dp,二維平面dp,狀態劃分dp

下午楊樂又講了序列劃分dp,樹形dp,狀態壓縮dp……

day 4

楊樂也走了,黃致煥來了

黃致煥講了棧,單調棧,隊列,單調隊列

day4就這樣愉快的結束了

day 5

黃致煥又講了並查集,堆,可並堆,樹狀數組,左偏樹等離奇數據結構。。。

並查集:

並查集是一種樹形數據結構支持合並,查找祖先

查找如下:

inline int find(int x) {  //father[x]表示x的祖先,father[x]=x表示x是根節點
    if (father[x] == x) return x;  //找到根節點
    return father[x] = find(father[x]);  //祖先的祖先就是我的祖先
}

合並如下:

inline void Union(int x,int y) { father[find(x)] = find(y); }  //合並祖先

堆:

定義:一棵滿足以下兩個性質的二叉樹:

1.父節點權值大於等於(大根堆)或小於等於(小根堆)任何一個兒子的權值。

2.每個節點的左子樹和右子樹都是一個堆。

左偏樹:

左偏樹是可並堆的一種

在左偏樹中,每個節點包含兩個屬性:權值和距離(dist)。除了之前二叉堆支持的操作以外,左偏樹還支持合並(merge)操作。

左偏樹的每個節點都滿足做兒子的dist大於右兒子的dist,每個節點的dist的值即為該子樹中與該節點最近的點的距離。

合並如下:

inline int merge(int x,int y) {
    if (!x || !y) return x|y;  //判斷空樹
    if (heap[x].key < heap[y].key) swap(x,y);  //讓鍵值大的為x
    heap[x].rson = merge(y,heap[x].rson);  //將右兒子與y合並
    if (heap[heap[x].lson].dist < heap[heap[x].rson].dist) swap(heap[x].lson,heap[x].rson);  //讓dist大的為左兒子
    heap[x].dist = heap[heap[x].rson].dist+1;  //算dist
    return x;
}

刪除如下:

inline int del(int x) { return merge(heap[x].lson,heap[x].rson); }  //將左右兒子合並

然後老師又講了很迷很迷的一些數據結構……

day 6

姜誌豪來了,圖論也來啦

樹的遍歷

BFS:一開始隊列中有一個點,將一個點出隊,將它的子結點全都入隊。

DFS:遞歸到一個點時,依次遞歸它的子結點。

最小生成樹

prim:

先隨機找一個點x作為根,然後維護一個集合S(存已經連通的點)和一個集合D(存當前連接S中所有點的線段,兩端點都在S中的除外)

初始化S={x},D={x所連接的所有邊};

每次在D中選一個權值最小的邊,然後將該邊刪去。該邊所連接的另一個點放入S中,直到S中點數=全部點數。

這裏記頂點數n,邊數m,

時間復雜度:O(m log n)

kruskal:

將邊權從小到大排序,每次選擇邊權最小的,如果當前邊連接的點已經連通了,就不選這條邊。

利用並查集維護是否連通。

m為圖中的邊數

時間復雜度:O(m log m)

最短路算法

floyd:

設dist[i][j]為i~j的距離,則有動態轉移方程:dist[i][j] = max{dist[i][k]+dist[k][j]}

dijkstra:

a.初始時,S只包含源點,即S={v},v的距離為0。U包含除v外的其他頂點,即:U={其余頂點},若v與U中頂點u有邊,則正常有權值,若u不是v的出邊鄰接點,則權值為∞。

b.從U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是v到k的最短路徑長度)。

c.以k為新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改後的距離值的頂點k的距離加上邊上的權。

d.重復步驟b和c直到所有頂點都包含在S中。

spfa:

首先建立一個數組s,s[i]代表從始點到i點所需要的最短距離。

利用一個隊列進行松弛操作:

初始化s[i]=∞;

首先將始點入隊,然後將始點所連接的點x入隊。入隊的時候,將始點到x的距離d賦值給s[i]。對於始點所連接的所有點都進行如下操作,彈出隊首元素。

訪問隊首元素y,將隊首元素y所連接的所有的點都入隊。假如其中一個點為z,則判定一下s[z]是否大於s[y]+< y,z >邊權。如果大於,則松弛一下,把s[y]賦值為較小的那個。

toposort:

入度:有多少條邊指向他

出度:有多少條邊從他發出

先統計每個點的入度,假如a->b,則in[b]++;

用棧來維護入度=0的點。

若棧非空,則在棧中彈出一個元素(並輸出),然後枚舉這個點能到的每一個點將它的入度-1.如果入度=0,則壓入棧中。

如果沒有輸出所有的頂點,則有向圖中一定存在環

強連通分量:

……

晚上,第二波考試

第一題:水題,n^2大暴力

第二題:不會做,仍然n^2大暴力

第三題:想到正解,寫炸了!!!

100+20+30=150 有大佬270 Orz %%%

day 7

Trie樹,KMP,AC自動機……

day 111

離noip還有1周……

暑假清北學堂集訓筆記