初涉倍增&&LCA【在更】
一种特殊的枚举算法
什么是倍增
顾名思义,即每一次翻倍增加。那么,这样我们就有了一种$O(logn)$阶的方法处理枚举方面的问题了。
参考:【白话系列】倍增算法
一些题目
【倍增】luoguP1613 跑路
题目描述
小A的工作不仅繁琐,更有苛刻的规定,要求小A每天早上在6:00之前到达公司,否则这个月工资清零。可是小A偏偏又有赖床的坏毛病。于是为了保住自己的工资,小A买了一个十分牛B的空间跑路器,每秒钟可以跑2^k千米(k是任意自然数)。当然,这个机器是用longint存的,所以总跑路长度不能超过maxlongint千米。小A的家到公司的路可以看做一个有向图,小A家为点1,公司为点n,每条边长度均为一千米。小A想每天能醒地尽量晚,所以让你帮他算算,他最少需要几秒才能到公司。数据保证1到n至少有一条路径。
数据范围
50%的数据满足最优解路径长度<=1000;
100%的数据满足n<=50,m<=10000,最优解路径长度<=maxlongint。
题目分析
首先,显然这题是不能求最短路的跑路次数的。那么我们用bool类型的$f[i][j][t]$表示$i$到$j$存在长度为$2^t$的路径。
这样做可以使得$dis[i][j]$表示的是从$i$到$j$的最短跑路次数,因而最后用floyd求解最短路就好了。
1 #include<bits/stdc++.h> 2 const int maxn = 61; 3 4 bool f[maxn][maxn][71]; 5 int dis[maxn][maxn]; 6 int n,m; 7 8 int main() 9 { 10 scanf("%d%d",&n,&m); 11 memset(dis, 0x3f3f3f3f, sizeof dis); 12 for (int i=1; i<=m; i++) 13 { 14 int x,y; 15 scanf("%d%d",&x,&y); 16 f[x][y][0] = 1; 17 dis[x][y] = 1; 18 } 19 for (int t=1; t<=64; t++) 20 for (int k=1; k<=n; k++) 21 for (int i=1; i<=n; i++) 22 for (int j=1; j<=n; j++) 23 if (f[i][k][t-1]&&f[k][j][t-1]){ 24 f[i][j][t] = 1; 25 dis[i][j] = 1; 26 } 27 for (int k=1; k<=n; k++) 28 for (int i=1; i<=n; i++) 29 for (int j=1; j<=n; j++) 30 if (dis[i][k]+dis[k][j] < dis[i][j]) 31 dis[i][j] = dis[i][k]+dis[k][j]; 32 printf("%d\n",dis[1][n]); 33 return 0; 34 }
【LCA】luoguP3379 【模板】最近公共祖先(LCA)
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入输出格式
输入格式:
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
题目分析
呃这是一个裸的LCA题,那么就讲讲模拟法和倍增法吧(RMQ暂且不提)
模拟法
为了查询LCA(x,y),我们从x开始一直向上染色。再从y开始一直向上查询,最先查询到的染色过的点就是LCA(x,y)。
有一个技巧,把染色的vis开成int数组,这样每一次就不用memset了(很慢的)
1 #include<bits/stdc++.h> 2 const int maxn = 500003; 3 4 std::vector<int> f[maxn]; 5 int fa[maxn]; 6 int n,m,s; 7 int vis[maxn]; 8 9 int read() 10 { 11 char ch = getchar(); 12 int num = 0; 13 for (; !isdigit(ch); ch = getchar()); 14 for (; isdigit(ch); ch = getchar()) 15 num = (num<<1)+(num<<3)+ch-48; 16 return num; 17 } 18 void buildStructure(int now) 19 { 20 for (unsigned int i=0; i<f[now].size(); i++) 21 if (!fa[f[now][i]]) 22 { 23 fa[f[now][i]] = now; 24 buildStructure(f[now][i]); 25 } 26 } 27 int main() 28 { 29 n = read(), m = read(), s = read(); 30 for (int i=1; i<n; i++) 31 { 32 int x = read(), y = read(); 33 f[x].push_back(y), f[y].push_back(x); 34 } 35 fa[s] = s; 36 buildStructure(s); 37 fa[s] = 0; 38 for (int i=1; i<=m; i++) 39 { 40 int x = read(), y = read(); 41 for (; x; x = fa[x]) vis[x] = i; 42 for (; y; y = fa[y]) 43 if (vis[y] == i) 44 break; 45 printf("%d\n",y); 46 } 47 return 0; 48 }
当然以上做法是过不了这题的
倍增法
倍增的话,算是一种对于模拟法的优化吧。
我们每一次不止跳一步,而是以$2^i$步数向上跳。
那么就需要O(nlogn)预处理一些东西,查询则是O(logn)的。
参考:
3.LCA详解
1 #include<bits/stdc++.h> 2 const int maxn = 500003; 3 const int logMaxn = 23; 4 5 int n,m,s; 6 int deep[maxn],p[maxn][logMaxn]; 7 std::vector<int> f[maxn]; 8 9 inline int read() 10 { 11 char ch = getchar(); 12 int num = 0; 13 for (; !isdigit(ch); ch = getchar()); 14 for (; isdigit(ch); ch = getchar()) 15 num = (num<<1)+(num<<3)+ch-48; 16 return num; 17 } 18 void dfs(int now, int fa) //递归预处理 19 { 20 deep[now] = deep[fa]+1; 21 p[now][0] = fa; 22 for (int i=1; (1<<i)<=deep[now]; i++) 23 p[now][i] = p[p[now][i-1]][i-1]; 24 for (unsigned int i=0; i<f[now].size(); i++) 25 if (f[now][i]!=fa) 26 dfs(f[now][i], now); 27 } 28 int lca(int x, int y) //倍增LCA 29 { 30 if (deep[x] > deep[y]) std::swap(x, y); 31 for (int i=logMaxn-1; i>=0; i--) 32 if (deep[x]<=deep[y]-(1<<i)) 33 y = p[y][i]; 34 if (x==y) return x; 35 for (int i=logMaxn-1; i>=0; i--) 36 if (p[x][i]==p[y][i]) 37 continue; 38 else x = p[x][i], y = p[y][i]; 39 return p[x][0]; 40 } 41 int main() 42 { 43 n = read(), m = read(), s = read(); 44 register int i,x,y; 45 for (i=1; i<n; i++) 46 { 47 x = read(), y = read(); 48 f[x].push_back(y), f[y].push_back(x); 49 } 50 dfs(s, 0); 51 for (i=1; i<=m; i++) 52 { 53 x = read(), y = read(); 54 printf("%d\n",lca(x, y)); 55 } 56 return 0; 57 }
【LCA】luoguP3398 仓鼠找sugar
题目描述
小仓鼠的和他的基(mei)友(zi)sugar住在地下洞穴中,每个节点的编号为1~n。地下洞穴是一个树形结构。这一天小仓鼠打算从从他的卧室(a)到餐厅(b),而他的基友同时要从他的卧室(c)到图书馆(d)。他们都会走最短路径。现在小仓鼠希望知道,有没有可能在某个地方,可以碰到他的基友?
小仓鼠那么弱,还要天天被zzq大爷虐,请你快来救救他吧!
输入输出格式
输入格式:
第一行两个正整数n和q,表示这棵树节点的个数和询问的个数。
接下来n-1行,每行两个正整数u和v,表示节点u到节点v之间有一条边。
接下来q行,每行四个正整数a、b、c和d,表示节点编号,也就是一次询问,其意义如上。
输出格式:
对于每个询问,如果有公共点,输出大写字母“Y”;否则输出“N”。
说明
__本题时限1s,内存限制128M,因新评测机速度较为接近NOIP评测机速度,请注意常数问题带来的影响。__
20%的数据 n<=200,q<=200
40%的数据 n<=2000,q<=2000
70%的数据 n<=50000,q<=50000
100%的数据 n<=100000,q<=100000
题目分析
题目大意就是判断树上(a,b)与(c,d)两条路径之间有没有交点。
看上去很唬人的样子对吧……
【待更】其实我也觉得很唬人