目录

  • 一、树的直径(Diameter)
    • 1.树形DP求树的直径
    • 2.两次BFS/DFS求树的直径
  • 1.POJ 1985.Cow Marathon(DFS求树的直径模板题)
  • 2.AcWing 350. 巡逻
  • 二、最近公共祖先(LCALCALCA)
  • 1.树上倍增法
  • (1) P3379 【模板】最近公共祖先(LCA)
  • (2)HDOJ2586 How far away(LCA)
  • 2.LCA的Tarjan算法
  • 三、树上差分
  • 1.P3258 [JLOI2014]松鼠的新家(树上差分模板)
  • 2.POJ3417 闇の連鎖
  • 3.luogu P4556雨天的尾巴 (树上对点差分 + 动态开点 + 线段树合并)线段树合并模板离线在线详解
  • 四、LCA的综合应用

声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了 按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。


下方链接为学习笔记目录链接(中转站)

学习笔记目录链接


ACM-ICPC在线模板


一、树的直径(Diameter)

树上两点的距离定义为,从树上一点到另一点所经过的权值

当树上两点距离最大时,就称作树的直径,树的直径既可以指这个权值,也可以指这个路径 (路径也叫树的最长链)。

树的直径有两种方法,都是O(n)O(n)O(n)的时间复杂度。

1.树形DP求树的直径

设以1号结点为根,那么n个结点n-1条边的无向图就可以看作一个有根树。
设D[x]D[x]D[x]表示从结点x 出发走向以x为根的子树,能够到达的最远结点的距离。
设x的子结点为y1,y2...yt,edge[x,y]y_1,y_2...y_t,edge[x,y]y1​,y2​...yt​,edge[x,y]表示边权
那么显然有:
D[x]=1≤i≤tmax(D[yi]+edge[x,yi])D[x] = ^{max}_{1≤i≤t}(D[y_i] + edge[x,y_i])D[x]=1≤i≤tmax​(D[yi​]+edge[x,yi​])

设F[x]F[x]F[x]为经过结点x的最长链的长度。
然后就是代码:

int ans;
int vis[N];
void dp(int u){vis[u] = 1;for(int i = head[u];i;i = nex[i]){int v = ver[i];if(vis[v])continue;dp(v);ans = max(ans,d[u] + d[v] + edge[i]);//看这条链是不是最大的d[x] = max(d[u],d[v] + edge[i]);//更新当前链长}
}

2.两次BFS/DFS求树的直径

我们可以先从任意一点开始DFS,记录下当前点所能到达的最远距离,这个点为P。

在从P开始DFS记录下所能达到的最远点的距离,这个点为Q。

P,QP,QP,Q就是直径的端点,dis(P,Q)dis(P,Q)dis(P,Q)就是直径。
具体代码见下题

1.POJ 1985.Cow Marathon(DFS求树的直径模板题)

题意:有N个农田以及M条路,给出M条路的长度以及路的方向(这道题不影响,用不到),让你找到一条 两农田(任意的)间的路径,使得距离最长,并输出最长距离。
这里用dfs求直径,当然也可以用bfs和树形DP来做。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 5e5+7;
const int M = 2007;int head[N],ver[N],tot,edge[N],nex[N];
int n,m,ans;
int dis[N],vis[N];inline void add(int u,int v,int w){ver[++tot] = v;edge[tot] = w;nex[tot] = head[u];head[u] = tot;
}//两次dfs一次求P一次求Q
void dfs(int u,int &ed){if(dis[u] > ans)ans = dis[u],ed = u;vis[u] = 1;for(int i = head[u];~i;i = nex[i]){int v = ver[i],w = edge[i];if(vis[v])continue;dis[v] = dis[u] + w;dfs(v,ed);}return ;
}int p,q;
void solve(){dfs(1,p);ans = dis[p] = 0;memset(vis,0,sizeof vis);dfs(p,q);cout<<ans<<endl;
}
int main()
{while(scanf("%d%d",&n,&m) != EOF){memset(head,-1,sizeof head);memset(vis,0,sizeof vis);memset(dis,0,sizeof dis);tot = 0;over(i,1,m){int u,v,w;char ch[2];scanf("%d%d%d%s",&u,&v,&w,ch);add(u,v,w);add(v,u,w);}solve();}
return 0;
}

2.AcWing 350. 巡逻

我的博客详解:https://fanfansann.blog.csdn.net/article/details/106735359

二、最近公共祖先(LCALCALCA)

搬一下我之前写的博客

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。 ———来自百度百科


比如这一颗二叉树,D和E的LCA很明显是根A,要注意的是D和B的LCA应该是B它本身

1.树上倍增法

设F[x,k]F[x,k]F[x,k]表示x的2k2^k2k辈祖先,若该结点不存在则F[x,k]=0F[x,k]=0F[x,k]=0。除此之外,∀k∈[1,logn],F[x,k]=F[F[x,k−1],k−1]\forall k \in [1,logn],F[x,k] = F[F[x,k-1],k-1]∀k∈[1,logn],F[x,k]=F[F[x,k−1],k−1]。
树上倍增法的预处理阶段时间复杂度为O(nlogn)O(nlogn)O(nlogn),之后多次对于不同的x,y进行询问LCA,每次询问的时间复杂度为O(logn)O(logn)O(logn)。

(1) P3379 【模板】最近公共祖先(LCA)

P3379 【模板】最近公共祖先(LCA)


要找两个节点的LCA,暴力走的话就一步一步地往上爬,当然时间复杂度会贼高,不可取,你会发现一步一步往上爬就跟开篇我分享的那一篇博客里写的小兔子往前走一模一样,所以同样可以用倍增算法来优化。

就是按2的倍数来增大,也就是跳 1,2,4,8,16,32……1,2,4,8,16,32……1,2,4,8,16,32…… 不过在这我们不是按从小到大跳,而是从大向小跳,即按……32,16,8,4,2,1……32,16,8,4,2,1……32,16,8,4,2,1来跳,如果大的跳不过去,再把它调小。这是因为从小开始跳,可能会出现“悔棋”的现象。拿 555 为例,从小向大跳,5≠1+2+45≠1+2+45​=1+2+4,所以我们还要回溯一步,然后才能得出5=1+45=1+45=1+4;而从大向小跳,直接可以得出5=4+15=4+15=4+1。这也可以拿二进制为例,5(101)5(101)5(101),从高位向低位填很简单,如果填了这位之后比原数大了,那就不填了,这个过程是很好操作的。

所以整体思路就是用倍增算法来优化往上跳的时间,先用一个dfs预处理一下树,把所有节点的深度,父节点和它的2i2^i2i级的祖先全部用数组存起来,方便直接跳

其中几个重要的数组:

  • depth数组是记录每个节点的深度
  • fa[i][j]fa[i][j]fa[i][j]是指节点 iii 的 2j2^j2j 级的祖先的编号
  • head数组是链式前向星的数组相信大家都会,这里就不展开了
  • lg数组是常数优化的数组,存的是log2N+1的值,注意用的时候要-1,开始之前先初始化一下,这样直接调用可以优化节约时间其中初始化的方法:lg[i]=lg[i−1]+(1<<lg[i−1]==i)lg[i]=lg[i-1]+(1<<lg[i-1]==i)lg[i]=lg[i−1]+(1<<lg[i−1]==i),自己手算一下很清楚的(lg[1~10]为1 2 2 3 3 3 3 4 4 4,应该很好懂吧)

预处理完了就要倍增求LCA了,我们先把两个点提到同一高度,再统一开始跳。

但我们在跳的时候不能直接跳到它们的LCA,因为这可能会误判,比如4和8,在跳的时候,我们可能会认为1是它们的LCA,但1只是它们的祖先,它们的LCA其实是3。所以我们要跳到它们LCA的下面一层,比如4和8,我们就跳到4和5,然后输出它们的父节点,这样就不会误判了。
然后就是代码了,里面藏着非常详细的注释,相信大家这么强一看就懂qwqqwqqwq

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<math.h>#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r)/2
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=500007;
const ll INF=1e9+9;
const ll mod=2147483647;
const double EPS=1e-10;//-10次方约等于趋近为0
const double Pi=3.1415926535897;
ll n,m;
struct node
{ll u,v,nex;
}e[N<<1];
ll head[N],cnt;void add(ll u,ll v)
{e[++cnt].v=v;e[cnt].u=u;//没什么用,还白占空间e[cnt].nex=head[u];head[u]=cnt;
}ll depth[N],fa[N][30],lg[N],s,x,y;/*dfs函数的作用就是更新该点的所有祖先的fa数组,并通过递归把
该节点的所有的子节点和该节点一样去更新*/
void dfs(ll now,ll fath)//子节点和父节点
{fa[now][0]=fath;//更新一下fa数组,2^0=1就是父节点depth[now]=depth[fath]+1;//更新深度over(i,1,lg[depth[now]]-1)fa[now][i]=fa[fa[now][i-1]][i-1];/*更新now的所有 2^i 级的祖先。先找到now的2^(i-1)级祖先,再往上找该祖先的2^(i-1)级祖先,就是now的2^i祖先,必须一节一节地往上搜*/for(ll i=head[now];i;i=e[i].nex)//链式前向星遍历//如果now有子节点的话,就递归往子节点的子节点走(禁止套娃)if(e[i].v!=fath)//if(deep[v])continue;dfs(e[i].v,now);
}inline ll LCA(ll x,ll y)
{if(depth[x]<depth[y])//用数学语言就是说不妨设x的深度比y的深度大swap(x,y);//这样下面只需要写一种代码就好了while(depth[x]>depth[y])//让x跳到y的高度(同一高度)x=fa[x][lg[depth[x]-depth[y]]-1];//如果跳到一块了那LCA肯定就是y了if(x==y)return x;for(ll k=lg[depth[x]]-1;k>=0;--k)//倒着从大到小地跳/*因为我们要求跳到x和y的LCA的下一层,所以没有跳到的时候就让x和y利用dfs里早就用倍增算法处理过的祖先路径快速地一块往上跳*/if(fa[x][k]!=fa[y][k])x=fa[x][k],y=fa[y][k];//往上跳return fa[x][0];//返回x,y的父节点(肯定是相同的嘛)
}int main()
{scanf("%lld%lld%lld",&n,&m,&s);over(i,1,n-1){scanf("%lld%lld",&x,&y);add(x,y),add(y,x);//无向图一定要记得建双向边}over(i,1,n)//预处理一下lg[i]=lg[i-1]+(1<<lg[i-1]==i);//log2(8)=3//这个手写的lg[]要-1才能用lg[8]=4;dfs(s,0);//从树根开始,因为用的是链式前向星所以给一个假想根0(其实就是到这儿停)//dfs一下,预处理各点的深度和祖先over(i,1,m){scanf("%lld%lld",&x,&y);printf("%lld\n",LCA(x,y));}return 0;
}

(2)HDOJ2586 How far away(LCA)

题目大意:n结点的树,输出任意两个结点间的最小距离
思路分析:求两个点的LCA,最小距离即deep[a]+deep[b]−2∗deep[lca]deep[a]+deep[b]-2*deep[lca]deep[a]+deep[b]−2∗deep[lca]

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 5e4+7;
const int M = 2007;int head[N],ver[N<<1],tot,edge[N<<1],nex[N<<1];
int n,m,ans,t;
int f[N][20],deep[N],dis[N];inline void add(int u,int v,int w){ver[++tot] = v;edge[tot] = w;nex[tot] = head[u];head[u] = tot;
}void init(){t = (int)(log(n) / log(2)) + 1;//以2为底的log2(n)over(i,1,n)head[i] = deep[i] = 0;//初始化deep深度tot = 0;
}queue<int>q;
void bfs(){q.push(1);deep[1] = 1;while(q.size()){int u = q.front();q.pop();for(int i = head[u];i;i = nex[i]){int v = ver[i],w = edge[i];if(deep[v])continue;deep[v] = deep[u] + 1;dis[v] = dis[u] + w;f[v][0] = u;//父结点for(int j = 1;j <= t;++j)f[v][j] = f[f[v][j-1]][j-1];q.push(v);}}
}int lca(int x,int y){if(deep[x] > deep[y])swap(x,y);lver(i,t,0)//先提到同一个高度if(deep[f[y][i]] >= deep[x])//倍增思想y = f[y][i];if(x == y)return x;lver(i,t,0)//再找公共祖先if(f[x][i] != f[y][i])x = f[x][i],y = f[y][i];return f[x][0];//return公共父结点
}
int T;
int main(){cin>>T;while(T--){scanf("%d%d",&n,&m);init();over(i,1,n-1){//n-1条边int x,y,z;scanf("%d%d%d",&x,&y,&z);add(x,y,z);add(y,x,z);}bfs();over(i,1,m){int x,y;scanf("%d%d",&x,&y);printf("%d\n",dis[x] + dis[y] - 2 * dis[lca(x,y)]);}}return 0;
}

2.LCA的Tarjan算法

LCA的Tarjan算法是离线算法,但时间复杂度也从树上倍增的O((n+m)logn)O((n+m)logn)O((n+m)logn)优化到了O(n+m)O(n+m)O(n+m)。虽然实测树上倍增更快一点,应该是意外
下面的代码是上面那道How far away的AC代码。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 5e4+7;
const int M = 2007;int head[N],ver[N<<1],tot,edge[N<<1],nex[N<<1];
int n,m,T,t;
int fa[N],dis[N],vis[N],ans[N];
vector<int>query[N],query_id[N];void add(int u,int v,int w){ver[++tot] = v;edge[tot] = w;nex[tot] = head[u];head[u] = tot;
}void add_query(int x,int y,int id){query[x].push_back(y);query_id[x].push_back(id);query[y].push_back(x),query_id[y].push_back(id);
}int Get(int x){if(x == fa[x])return x;return fa[x] = Get(fa[x]);
}void tarjan(int u){vis[u] = 1;for(int i = head[u];i;i = nex[i]){int v = ver[i];if(vis[v])continue;dis[v] = dis[u] + edge[i];tarjan(v);fa[v] = u;}for(int i = 0;i < query[u].size();++i){int v = query[u][i],id = query_id[u][i];if(vis[v] == 2){int lca = Get(v);ans[id] = min(ans[id],dis[v] + dis[u] - 2 * dis[lca]);}}vis[u] = 2;
}int main()
{cin>>T;while(T--){scanf("%d%d",&n,&m);over(i,1,n){head[i] = 0,fa[i] = i,vis[i] = 0;query[i].clear(),query_id[i].clear();}tot = 0;over(i,1,n-1){int x,y,z;scanf("%d%d%d",&x,&y,&z);add(x,y,z);add(y,x,z);}over(i,1,m){int x,y;scanf("%d%d",&x,&y);if(x == y)ans[i] = 0;else {add_query(x,y,i);ans[i] = 1<<30;}}tarjan(1);over(i,1,m)printf("%d\n",ans[i]);}return 0;
}

三、树上差分

对边差分

  • (u,v)上全部加上w,对于差分数组就是:
  • u加上w,v加上w,lca减去2 × w
  • 用子树中差分数组的和来还原信息
  • 每个点的信息记录的是其到父亲的边的信息

对点差分

  • (u,v)上全部加上w,对于差分数组就是:
  • u加上w,v加上w,lca减去w,Fatherlca减去w
  • 同样用子树中差分数组的和来还原信息

差分和数据结构结合

  • 对于一个支持单点修改、区间求和的数据结构,如果使用差分, 就可以支持区间加法、单点查询
  • 甚至可以支持区间加法、区间求和
  • 一个经典的例子就是用树状数组来完成这些事情
  • 用DFS序还可以把放到树上,区间变成子树

1.P3258 [JLOI2014]松鼠的新家(树上差分模板)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 3e5+7;
const int M = 2007;int head[N<<1],ver[N<<1],tot,nex[N<<1];
int n,m,T,t;
int f[N][30],vis[N];
int deep[N];
int s[N];//差分数组
int a[N];void add(int u,int v){ver[++tot] = v;nex[tot] = head[u];head[u] = tot;
}queue<int>q;void bfs(){//lca的预处理q.push(1);deep[1] = 1;//根的深度为1while(q.size()){int u = q.front();q.pop();for(int i = head[u];i;i = nex[i]){int v = ver[i];if(deep[v])continue;f[v][0] = u;deep[v] = deep[u] + 1;for(int j = 1;j <= t;++j)f[v][j] = f[f[v][j-1]][j-1];q.push(v);}}
}int lca(int x,int y){if(deep[x] > deep[y])swap(x,y);lver(i,t,0)if(deep[f[y][i]] >= deep[x])y = f[y][i];if(x == y)return x;lver(i,t,0)if(f[x][i] != f[y][i])x = f[x][i],y = f[y][i];return f[x][0];
}void dfs(int u,int fa){for(int i = head[u];i;i = nex[i]){int v = ver[i];if(v == fa)continue;dfs(v,u);s[u] += s[v];}
}int main()
{cin>>n;t = (int)(log(n) / log(2)) + 1;over(i,1,n)scanf("%d",&a[i]);over(i,1,n-1){int x,y;scanf("%d%d",&x,&y);add(x,y);add(y,x);}bfs();over(i,1,n-1){s[a[i]]++;s[a[i+1]]++;s[lca(a[i],a[i+1])]--;s[f[lca(a[i],a[i+1])][0]]--;}dfs(1,0);//从差分数组还原至原数组over(i,2,n)s[a[i]]--;//我们是直接正序循环一遍,这样从2开始每个点都是即当一次起点又当一次终点多加了一次over(i,1,n)printf("%d\n",s[i]);return 0;
}

2.POJ3417 闇の連鎖

3.luogu P4556雨天的尾巴 (树上对点差分 + 动态开点 + 线段树合并)线段树合并模板离线在线详解


https://fanfansann.blog.csdn.net/article/details/106729640

四、LCA的综合应用

0x63.图论 - 树的直径与最近公共祖先相关推荐

  1. 图论--树的直径--DFS+树形DP模板

    #include <iostream> #include <cstring> using namespace std;//maxv:源点能到的最远点,maxdis:最远点对应的 ...

  2. 与图论的邂逅05:最近公共祖先LCA

    什么是LCA? 祖先链 对于一棵树T,若它的根节点是r,对于任意一个树上的节点x,从r走到x的路径是唯一的(显然),那么这条路径上的点都是并且只有这些点是x的祖先.这些点组成的链(或者说路径)就是x的 ...

  3. 树:寻找最近的公共祖先

    前引: 树可以分为好几种:普通树,二叉树(二叉链,三叉链),二叉搜索树等等,今天我们,讨论的的问题可就和就几种树有关. 我们先易后难,讨论二叉搜索树! 且所有的输入满足 所有节点的值都是唯一的. p. ...

  4. LCA树两个节点最低公共祖先

    #include<iostream> #include<vector> using namespace std;struct Tree {Tree *pLeft;Tree *p ...

  5. 树上问题(一)倍增算法求最近公共祖先

    倍增算法求最近公共祖先 一.概述 在图论和计算机科学中,最近公共祖先 LCA(Least Common Ancestors)是指在一个树或者有向无环图中同时拥有v和w作为后代的最深的节点.在这里,我们 ...

  6. NOIp 图论算法专题总结 (1):最短路、最小生成树、最近公共祖先

    系列索引: NOIp 图论算法专题总结 (1) NOIp 图论算法专题总结 (2) NOIp 图论算法专题总结 (3) 最短路 Floyd 基本思路:枚举所有点与点的中点,如果从中点走最短,更新两点间 ...

  7. 求树的直径算法以及证明

    以下为两次dfs(bfs)的做法以及正确性证明. 算法步骤 (1)任取树上一点S,以S为源点BFS得S到各个顶点的d值: (2)取d值最大者之一为P,再以P为源点BFS得P到各个顶点的d值: (3)再 ...

  8. 洛谷 P4408 [NOI2003] 逃学的小孩(树的直径)

    [NOI2003] 逃学的小孩 题目描述 Chris 家的电话铃响起了,里面传出了 Chris 的老师焦急的声音:"喂,是 Chris 的家长吗?你们的孩子又没来上课,不想参加考试了吗?&q ...

  9. 树的直径,树的重心,树的分冶

    主要是利用了反证法: 假设 s-t这条路径为树的直径,或者称为树上的最长路 现有结论,从任意一点u出发搜到的最远的点一定是s.t中的一点,然后在从这个最远点开始搜,就可以搜到另一个最长路的端点,即用两 ...

最新文章

  1. 硬核!一文梳理经典图网络模型
  2. SpringBoot笔记1-使用idea创建SpringBoot的hello world
  3. TFTP更新linux或android系统文件
  4. 第十一届蓝桥杯赛后总结 —— 两年征战蓝桥,惜败来年再战。
  5. 29. Divide Two Integers
  6. 单独安装想要的office_安装OFFICE不再求人,最省心的方法
  7. SQL 语句执行顺序
  8. glassfish显示不了html文件,Glassfish websocket无法正常工作(示例代码)
  9. 慕课学习--DNS的作用
  10. 网络基础知识(面试基础)
  11. 塞规公差带图_螺纹塞规公差及尺寸表
  12. 实验吧CTF web刷题
  13. 查找微信公众号服务器地址,手把手教大家搭建微信公众号查题功能
  14. 为什么域名能够访问网站,而直接使用IP不可以
  15. 初涉Workflow(2)——XPDL
  16. java newline_Java 输出文件通过 BufferedWriter.newline() 方法换行
  17. 哪里有kitti数据集的百度云资源
  18. BZOJ 1208: [HNOI2004]宠物收养所 (Treap)
  19. 从键盘输入一个英文字母,进行大小写字母转换,并输出。
  20. opencv + contrib windows下源码编译

热门文章

  1. 使用霍夫变换检测车道线
  2. 实战:基于深度学习和几何的3D边界框估计
  3. 各种Optimizer梯度下降优化算法回顾和总结
  4. 神器!3小时复现 Alexnet 和 word2vec!
  5. 不明原因的约束报错的两种处理方式
  6. 国民认证科技有限公司助力构建我国可信网络空间
  7. C# 窗体位置 Show和ShowDialog (转载)
  8. mysql 索引 死锁,由不同的索引更新解决MySQL死锁套路
  9. python字典的实现原理_Python字典的实现原理
  10. grails springboot_groovy 使用spring boot