简述

对于初学 Tarjan 的你来说,肯定和我一开始学 Tarjan 一样无比迷茫。

网上大框大框的定义就足以让一个萌新从入门到入土自闭。

所以本文决定不在这里对于 Tarjan 的定义和原理做过多介绍。(当然如果还是无法理解可以尝试直接理解代码)

注意&特别鸣谢:这篇文章,本文也有多处讲解与图片转自此文

作用

其实这都是后话了...。(毕竟你不会这个东西知道它的作用也没什么用)

下面是一段令人窒息的专业定义,萌新慎入:

  1. 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。
  2. 如果有向图G的每两个顶点都强连通,称G是一个强连通图
  3. 非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

所以 Tarjan 的基础用处就是:求一个图的强连通分量

举个栗子:

下图中,子图 \(\{1,2,3,4\}\) 为一个强连通分量,因为顶点 \(1,2,3,4\) 两两可达。\(\{5\},\{6\}\)也分别是两个强连通分量。

让我们带着疑惑和不解去探索 Tarjan 的奥秘吧。

Tarjan 算法

原理

Tarjan 算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。

搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

当然,主要还是靠下面两个标记。

出场人物

其实就是用到了哪几个变量。

  1. \(Dfn(u)\):为节点u搜索的次序编号(时间戳)。
  2. \(Low(u)\):为 \(u\) 或 \(u\) 的子树能够追溯到的最早的栈中节点的次序号(人话:\(u\) 的子树以及其连向的节点中 \(dfn[]\) 的最小值)。

由定义可以得出:

Low(u)=Min
{
DFN(u),
Low(v),(u,v)为树枝边,u为v的父节点
DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)
}

当 \(Dfn(u)=Low(u)\) 时,以 \(u\) 为根的搜索子树上所有节点是一个强连通分量。

感性理解一下,当一个集合 \(S\) 构成强连通分量时,它们的祖先节点肯定是 \(dfn[]\) 最小的。(遍历时间最早)

那么除了这个祖先节点之外,其他所有节点的 \(low[]\) 肯定都会被刷新。

所以唯一不变的就是祖先节点,找到祖先节点也就找到了整个强连通分量,而栈中的残留数字都属于 \(S\) 集合。

图示

从节点 \(1\) 开始 DFS,把遍历到的节点加入栈中。

搜索到节点 \(u=6\) 时,\(Dfn[6]=Low[6]\),找到了一个强连通分量。

退栈到 \(u=v\) 为止,\(\{6\}\)为一个强连通分量。

返回节点 \(5\),发现 \(Dfn[5]=Low[5]\),退栈后 \(\{5\}\) 为一个强连通分量。

返回节点 \(3\),继续搜索到节点 \(4\),把 \(4\) 加入堆栈。

发现节点 \(4\) 向节点 \(1\) 有后向边,节点 \(1\) 还在栈中,所以 \(Low[4]=1\)。

节点 \(6\) 已经出栈,\((4,6)\) 是横叉边,返回 \(3\),\((3,4)\) 为树枝边,所以 \(Low[3]=Low[4]=1\)。

继续回到节点 \(1\),最后访问节点 \(2\)。访问边 \((2,4)\),\(4\) 还在栈中,所以 \(Low[2]=Dfn[4]=5\)。

返回 \(1\) 后,发现 \(Dfn[1]=Low[1]\),把栈中节点全部取出,组成一个连通分量 \(\{1,3,4,2\}\)。

至此,算法结束。经过该算法,求出了图中全部的三个强连通分量 \(\{1,3,4,2\},\{5\},\{6\}\)。

可以发现,运行 Tarjan 算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为 \(O(N+M)\)。

代码实现

代码实现很简单(前提是你看懂了上面的讲解,也不排除很多同学可以直接通过代码加深理解)。

//co[] 表示这个强联通分量的“颜色”,sum[] 表示这个“颜色”的强连通分量的节点个数。
//num 是时间戳,tot 是栈顶,col 是“颜色”总数。
void tarjan(int u){
dfn[u]=low[u]=++num;//打上时间戳,并同时给 low[u] 赋初值。
s[++tot]=u;//进栈。
for(int i=head[u];i;i=ed[i].nxt){
int v=ed[i].to;
if(!dfn[v])//如果没有遍历过(即这是一条树枝边)。
tarjan(v),low[u]=min(low[u],low[v]);
else if(!co[v])//遍历过(即这是一条返祖变或横向边)。
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){//找到啦。
co[u]=++col,sum[col]=a[u];
while(u!=s[tot])
sum[col]+=a[s[tot]],co[s[tot]]=col,--tot;
--tot;
}
return;
} for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);

例题

多做例题通常是了解新算法的重要过程,建议读者完全消化上面的模板后在食用。

题目都不难,而且十分适合初学者。

例题一

受欢迎的牛

简化题意:

有一个包括 \(N\) 个点,\(M\) 条边的有向图,求所有节点都能到达的节点的个数。如果没有,则输出 0。

分析题目,这样的节点只可能出现在唯一一个出度为 0 的强联通分量内。

若出现两个以上出度为 0 的强连通分量则不存在这样的节点,因为那几个出度为零的分量无法传递出去。

而分量内的节点一定是可以相互到达的。

那么题目就变为求:图中出度为 0 的强联通分量的节点总数,如果有多个这样的强连通分量则输出 0

标准的 Tarjan 模板题。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#define N 10010
#define M 50010
using namespace std; int n,m,head[N],cnt=0;
int col=0,num=0;
int co[N],dfn[N],low[N],s[N],de[N],sum[N];
struct Edge{
int nxt,to;
}ed[M]; int read(){
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
return x*f;
} void add(int u,int v){
cnt++;
ed[cnt].nxt=head[u];
ed[cnt].to=v;
head[u]=cnt;
return;
} int tot=0; void tarjan(int u){
dfn[u]=low[u]=++num;
tot++;
s[tot]=u;
for(int i=head[u];i;i=ed[i].nxt){
int v=ed[i].to;
if(!dfn[v])
tarjan(v),low[u]=min(low[u],low[v]);
else if(!co[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
co[u]=++col;
++sum[col];
while(s[tot]!=u)
++sum[col],co[s[tot]]=col,--tot;
--tot;
}
return;
} int main(){
n=read(),m=read();
int u,v;
for(int i=1;i<=m;i++)
u=read(),v=read(),add(u,v);
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
int ans,k=0;
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=ed[j].nxt)
if(co[i]!=co[ed[j].to])
de[co[i]]++;
for(int i=1;i<=col;i++)
if(!de[i])
ans=sum[i],k++;
if(k==1) printf("%d\n",ans);
else puts("0");
return 0;
}

例题二

最大半连通子图

对于初学者来说可能有一定的难度,码量也是 100+ 行。

简单来说,这道题是有向图中常用的套路:Tarjan + 拓扑排序 + DP

定义挺长的,理解起来比较困难,但是读懂后就发现是水题了:缩点后求有向图中的最长链

我们先用模板式的 Tarjan 把图进行缩点。

然后我们对这个缩过点的图重新进行建边(注意:重新建图需要判重边,否则最后最长链的数量会受到影响)。

最后我们拓扑加 DP 把最长链的节点的数量和最长链的数量求出。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#define N 100010
#define M 1000010
using namespace std; int n, m, MOD, head[N], cnt = 0;
int low[N], dfn[N], s[N], co[N], sum[N];
int tot = 0, numm = 0, col = 0;
struct Edge {
int nxt, to;
} ed[M]; int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
} void add(int u, int v) {
++cnt;
ed[cnt].nxt = head[u];
ed[cnt].to = v;
head[u] = cnt;
return;
} void tarjan(int u) {
dfn[u] = low[u] = ++numm;
s[++tot] = u;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v])
low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
co[u] = ++col;
++sum[col];
while (s[tot] != u) ++sum[col], co[s[tot]] = col, --tot;
--tot;
}
return;
} int nu[M], x[M], y[M], ru[N]; bool cmp(int a, int b) {
if (x[a] != x[b])
return x[a] < x[b];
return y[a] < y[b];
} void remove() {
for (int i = 1; i <= m; i++) {
nu[i] = i;
x[i] = co[x[i]];
y[i] = co[y[i]];
}
sort(nu + 1, nu + m + 1, cmp);
cnt = 0;
memset(head, 0, sizeof(head));
memset(ed, 0, sizeof(ed));
for (int i = 1; i <= m; i++) {
int z = nu[i];
if (x[z] != y[z] && (x[z] != x[nu[i - 1]] || y[z] != y[nu[i - 1]])) {
add(x[z], y[z]);
++ru[y[z]];
}
}
return;
} int dis[N], num[N], ans; void topo() {
queue<int> q;
for (int i = 1; i <= col; i++) {
if (!ru[i]) {
q.push(i), dis[i] = sum[i], num[i] = 1;
if (dis[ans] < dis[i])
ans = i;
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (dis[v] < dis[u] + sum[v]) {
dis[v] = dis[u] + sum[v];
num[v] = 0;
if (dis[ans] < dis[v])
ans = v;
}
if (dis[v] == dis[u] + sum[v])
num[v] = (num[v] + num[u]) % MOD;
if (!--ru[v])
q.push(v);
}
}
return;
} int anss; void ask() {
for (int i = 1; i <= n; i++)
if (dis[i] == dis[ans])
anss = (anss + num[i]) % MOD;
} int main() {
n = read(), m = read(), MOD = read();
int u, v;
for (int i = 1; i <= m; i++) {
x[i] = read(), y[i] = read();
add(x[i], y[i]);
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
remove();
topo();
ask();
printf("%d\n%d\n", dis[ans], anss);
return 0;
}

例题三

网络协议

Tarjan 和 DAG 是有不解之缘的,所以经常通过 DAG 的某些性质来做题。

其实如果没有第二问的话,这道题就十分的简单了。

  1. 第一问:Tarjan 缩点后寻找入度为 0 的强连通分量的个数。
  2. 第二问:\(max(sum_{入度为 0 的强连通分量},sum_{出度为 0 的强连通分量})\)。

那么第二问的为什么可以这么求呢?

定理:将 DAG 变为有向连通图的最小方案数为 \(max(sum_{入度为 0 的强连通分量},sum_{出度为 0 的强连通分量})\)。

构造:连接 \(min(sum_{入度为 0 的强连通分量},sum_{出度为 0 的强连通分量})\) 个入度为 0 与出度为 0 的强连通分量构成环,

然后再随意连接剩余的入度为 0 或出度为 0 的强连通分量到环上任意节点。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
#define N 110
using namespace std; int n, head[N], cnt = 0;
int low[N], dfn[N], s[N], co[N], sum[N], col = 0, tot = 0, num = 0;
struct Edge {
int nxt, to;
} ed[N * N]; int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
} void add(int u, int v) {
ed[++cnt].nxt = head[u];
ed[cnt].to = v;
head[u] = cnt;
return;
} void tarjan(int u) {
dfn[u] = low[u] = ++num;
s[++tot] = u;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v])
low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
co[u] = ++col;
++sum[col];
while (s[tot] != u) ++sum[col], co[s[tot]] = col, --tot;
--tot;
}
return;
} int ru[N], cu[N]; int main() {
n = read();
int v;
for (int u = 1; u <= n; u++) {
v = read();
while (v) add(u, v), v = read();
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
if (col == 1) {
printf("1\n0\n");
return 0;
}
for (int u = 1; u <= n; u++)
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (co[u] != co[v])
++cu[co[u]], ++ru[co[v]];
}
int a = 0, b = 0;
for (int i = 1; i <= col; i++) {
if (!ru[i])
++a;
if (!cu[i])
++b;
}
printf("%d\n%d\n", a, max(a, b));
return 0;
}

例题四

间谍网络

这道题没有看上去那么简单,与普通缩点题唯一的不同是它加上了限制条件。

那怎么办?其实我们在循环是也可以加上限制条件,同时通过 \(dfn[]\) 判重(具体见代码)。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#define N 3010
#define M 8010
using namespace std; int n, m1, m2, head[N], cnt = 0, cost[N];
int low[N], dfn[N], co[N], s[N], ru[N], sum[N];
int col = 0, tot = 0, num = 0;
struct Edge {
int nxt, to;
} ed[M]; int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
} void add(int u, int v) {
ed[++cnt].nxt = head[u];
ed[cnt].to = v;
head[u] = cnt;
return;
} void init() {
n = read(), m1 = read();
int u, v;
memset(cost, 0x3f, sizeof(cost));
memset(sum, 0x3f, sizeof(sum));
for (int i = 1; i <= m1; i++) u = read(), v = read(), cost[u] = v;
m2 = read();
for (int i = 1; i <= m2; i++) u = read(), v = read(), add(u, v);
return;
} void tarjan(int u) {
dfn[u] = low[u] = ++num;
s[++tot] = u;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
co[u] = ++col, sum[col] = cost[u];
while (s[tot] != u) sum[col] = min(sum[col], cost[s[tot]]), co[s[tot]] = col, --tot;
--tot;
}
return;
} int main() {
init();
for (int i = 1; i <= n; i++)
if (!dfn[i] && cost[i] != 0x3f3f3f3f)//限制条件。
tarjan(i);
for (int i = 1; i <= n; i++)
if (!dfn[i]) {//判断无解。
printf("NO\n%d\n", i);
return 0;
}
for (int u = 1; u <= n; u++)
for (int i = head[u]; i; i = ed[i].nxt)
if (co[ed[i].to] != co[u])
++ru[co[ed[i].to]];
int ans = 0;
for (int i = 1; i <= col; i++)
if (!ru[i])
ans += sum[i];
printf("YES\n%d\n", ans);
return 0;
}

例题五

抢掠计划

首先日常缩点,重新建立有向图,然后...,有一个神仙做法:

  1. 化点权为边权:举个栗子,假设有边 \(u\to v\),\(v\) 的点权为 \(w\),那么令 \(edge(u,v)=-w\)。
  2. 因为置了负边权,所以将最长路变为熟悉的最短路,跑 SPFA 即可。
  3. 找到有酒吧的点中 \(-dis[]\) 最大的即可。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <queue>
#define N 500010
using namespace std; int n, m, S, P, x[N], y[N], a[N], head[N], cnt = 0;
int low[N], dfn[N], co[N], s[N], sum[N];
int col = 0, tot = 0, num = 0;
bool bar[N];
struct Edge {
int nxt, to, val;
} ed[N]; int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
} void add(int u, int v) {
ed[++cnt].nxt = head[u];
ed[cnt].to = v;
head[u] = cnt;
return;
} void tarjan(int u) {
dfn[u] = low[u] = ++num;
s[++tot] = u;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (!co[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
co[u] = ++col, sum[col] = a[u];
while (u != s[tot]) sum[col] += a[s[tot]], co[s[tot]] = col, --tot;
--tot;
}
return;
} void addedge(int u, int v, int w) {
ed[++cnt].nxt = head[u];
ed[cnt].to = v;
ed[cnt].val = w;
head[u] = cnt;
return;
} void Remove() {
cnt = 0;
memset(head, 0, sizeof(head));
memset(ed, 0, sizeof(ed));
for (int i = 1; i <= m; i++) {
if (co[x[i]] != co[y[i]])
addedge(co[x[i]], co[y[i]], -sum[co[y[i]]]);
}
return;
} int dis[N];
bool vis[N];
void SPFA(int s) {
queue<int> q;
memset(dis, 0x3f, sizeof(dis));
memset(vis, false, sizeof(vis));
s = co[s];
dis[s] = -sum[s];
vis[s] = true;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int i = head[u]; i; i = ed[i].nxt) {
int v = ed[i].to, w = ed[i].val;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v])
q.push(v), vis[v] = true;
}
}
}
return;
} int main() {
n = read(), m = read();
int u, v;
for (int i = 1; i <= m; i++) x[i] = read(), y[i] = read(), add(x[i], y[i]);
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
Remove();
S = read();
SPFA(S);
P = read();
int ans = 0;
for (int i = 1; i <= P; i++) u = read(), ans = max(ans, -dis[co[u]]);
printf("%d\n", ans);
return 0;
}

总结

图论中常用的算法之一就是 Tarjan 了,所以也是很重要的啦。

希望这篇文章对每一位读者都有帮助。

再次注意&特别鸣谢:这篇文章,本文也有多处讲解与图片转自此文

完结撒花。

浅谈 Tarjan 算法的更多相关文章

  1. 浅谈Tarjan算法

    从这里开始 预备知识 两个数组 Tarjan 算法的应用 求割点和割边 求点-双连通分量 求边-双连通分量 求强连通分量 预备知识 设无向图$G_{0} = (V_{0}, E_{0})$,其中$V_ ...

  2. 浅谈Tarjan算法及思想

    在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连 ...

  3. 浅谈 Tarjan 算法之强连通分量(危

    引子 果然老师们都只看标签拉题... 2020.8.19新初二的题集中出现了一道题目(现已除名),叫做Running In The Sky. OJ上叫绮丽的天空 发现需要处理环,然后通过一些神奇的渠道 ...

  4. 浅谈分词算法(5)基于字的分词方法(bi-LSTM)

    目录 前言 目录 循环神经网络 基于LSTM的分词 Embedding 数据预处理 模型 如何添加用户词典 前言 很早便规划的浅谈分词算法,总共分为了五个部分,想聊聊自己在各种场景中使用到的分词方法做 ...

  5. 浅谈分词算法(4)基于字的分词方法(CRF)

    目录 前言 目录 条件随机场(conditional random field CRF) 核心点 线性链条件随机场 简化形式 CRF分词 CRF VS HMM 代码实现 训练代码 实验结果 参考文献 ...

  6. 浅谈分词算法(3)基于字的分词方法(HMM)

    目录 前言 目录 隐马尔可夫模型(Hidden Markov Model,HMM) HMM分词 两个假设 Viterbi算法 代码实现 实现效果 完整代码 参考文献 前言 在浅谈分词算法(1)分词中的 ...

  7. 浅谈分词算法基于字的分词方法(HMM)

    前言 在浅谈分词算法(1)分词中的基本问题我们讨论过基于词典的分词和基于字的分词两大类,在浅谈分词算法(2)基于词典的分词方法文中我们利用n-gram实现了基于词典的分词方法.在(1)中,我们也讨论了 ...

  8. 浅谈Manacher算法与扩展KMP之间的联系

    首先,在谈到Manacher算法之前,我们先来看一个小问题:给定一个字符串S,求该字符串的最长回文子串的长度.对于该问题的求解.网上解法颇多.时间复杂度也不尽同样,这里列述几种常见的解法. 解法一   ...

  9. 浅谈KMP算法及其next[]数组

    KMP算法是众多优秀的模式串匹配算法中较早诞生的一个,也是相对最为人所知的一个. 算法实现简单,运行效率高,时间复杂度为O(n+m)(n和m分别为目标串和模式串的长度) 当字符串长度和字符集大小的比值 ...

随机推荐

  1. 036 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 03 嵌套if结构

    036 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 03 嵌套if结构 本文知识点:Java中的嵌套if结构 什么是嵌套if结构? 概念: 嵌套if结构 ...

  2. 二进制部署Redis-5.07

    Redis简介 Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理. 它支持字符串.哈希表.列表.集合.有序集合,位图,hyperloglogs等数据类 ...

  3. 「Redis」字符串

    原文链接:https://www.changxuan.top/?p=1109 简介 Redis 中自定义的字符串结构. 字符串是 Redis 中最常用的一种数据类型,在 Redis 中专门封装了一个字 ...

  4. linxu 命令

    top | grep java 统计 java 进程使用的资源比率 nohub java -jar test.war & 后台运行 test.war 程序,标准输出到 test.war 程序目 ...

  5. kafka配置文件详解

    kafka的配置分为 broker.producter.consumer三个不同的配置 一 .BROKER 的全局配置最为核心的三个配置 broker.id.log.dir.zookeeper.con ...

  6. 实验一 HTML基本标签及文本处理

    实验一 HTML基本标签及文本处理 [实验目的] 1.掌握利用因特网进行信息游览.搜索,下载网页.图片.文字和文件: 2.对给定的网站,能指出网站的链接结构.目录结构.页面布局方式: 3.掌握HTML ...

  7. (数据科学学习手札97)掌握pandas中的transform

    本文示例文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 开门见山,在pandas中,transform是 ...

  8. day18 Pyhton学习 匿名函数

    匿名函数 别称: lambda表达式 函数,没有名字 def wahaha(n):#wahaha return n**2 print(wahaha.__name__) qqxing = lambda ...

  9. nullptr解决了什么问题

    从0到NULL 在C++的世界中字面值0用来表示空指针,所以0可以当作所有指针类型的字面值.为了让语义更明确引入了NULL宏定义: #undef NULL #ifdef __cplusplus #de ...

  10. 【贪心算法】HDU 5969 最大的位或

    题目内容 Vjudge链接 给出一个闭区间,找该区间内两个数,使这两个数的按位或最大. 输入格式 包含至多\(10001\)组测试数据. 第一行有一个正整数,表示数据的组数. 接下来每一行表示一组数据 ...