首页 > 其他分享 >图论2023年版

图论2023年版

时间:2023-08-09 20:47:01浏览次数:24  
标签:图论 int 路径 MAXN 年版 2023 push include dis

基本概念

什么是图?图实际上是一个二元组\(G=(V,E)\),其中V是图中所有的点形成的点集,而E是边集
每一条边都可以描述成(u,v),u和v都是V中的元素 (v,w∈V)
点数,即V中元素个数也称为G的
图的分类

  1. 图可以按照边有无方向分类,分为有向图无向图
    无向图: 每条边都是无向边的图称为无向图
    有向图: 每条边都是有向边的图称为有向图
    如果一张图里有的边是有向的,有的是无向的,那它是混合图
  2. 图可以按照边的数量分为,简单图多重图
    简单图:没有自环和重边的图
    简单图:有自环和重边的图

图的度
image
在无向图中,与这个结点相连的边的个数,称为结点的度。比如在图1-1中,B的度数为4
在有向图中,结点的度分为入度和出度。
结点的入度:以这个结点为终点的有向边的个数
结点的出度:以这个结点为起点的有向边的个数
性质:图的总度数=边数*2

路径
途径:一个边的序列,满足后一条边的起点是前一条边的终点
: 如果一条途径中的边互不相同,就可以被称为迹
路径: 一条迹中,如果经过的点互不相同,就可以被称为“路径”
回路: 起点和终点相同的路径,称为“回路”
: 若回路中除了起点、重点外,其他点互不相同,称为“环”

连通
无向图中:从任意点,都存在到达其它点的路径,称为连通图。
有向图中:从任意点,都存在到达其它点的路径,称该有向图是强连通的。

子图
对于图G和图G’,如果图G’ 的边集和点集都是图G的边集和点集的子集,那么图G’是图G的子图

导出子图
对于图G和图G’,如果图G’ 的点集都是图G点集的子集,那么图G’是图G的导出子图,其边集不一定是图G边集的子集。

补图
对于图G和图G’,如果2个图的点集相同,边集没有交集,并且边集为完全图的边集,那么图G和图G’ 互为补集。

其它概念

  • 权值: 可以看成是边的长度
  • 完全图: 无向图中,如果任意两点之间都存在一条边,则是一个无向完全图,无向完全图有\(n*(n-1)/2\)条边;有向图中,如果任意两点之间都存在互通的两条边,则是一个有向完全图,有向完全图有\(n*(n-1)\)条边。
  • 稠密图: 边数接近于完全图的图
  • 稀疏图: 边数远远少于完全图的图
  • 强连通分量: 有向图中任意两点都连通的最大子图。

图的存储方式
邻接矩阵:int \(G[i][j]\); //\(G[i][j]\)表示从点\(i\)到点\(j\)的权值,定义如下

G[i][j] 有边 没有边
有权图 权值 正无穷
无权图 1 0

邻接表:邻接矩阵存储的缺点在于,当图是稀疏图时,邻接矩阵的存储效率很低。为此,我们图有另外一种存储方式,邻接表。
无权图

vector<int> g[MAXN];
g[i].push_back(j);//i→j 存在有向边
//g[j].push_back(i); 如果是无向图,该代码也得加上。
#include <vector>
vector<int> G[5];
G[0].push_back(1);
G[1].push_back(3);
G[2].push_back(1);
G[3].push_back(0);
G[3].push_back(2);
G[4].push_back(3);

有权图

struct edge{
  int to;
  int w;
}
vector<edge> g[MAXN];
g[i].push_back((edge){j,w});//i→j 存在有向边,权值为w
g[j].push_back((edge){i,w});//如果是无向图,该代码也得加上。

当然还有直接存边链式前向星两种方法
直接存边很好理解,就是直接用结构体存下边的起点、终点和权值(无向图也可不及)
缺点是效率较为低下
链式前向星其实就是用链表实现的邻接矩阵,代码就不给了

复杂度 找边 遍历一点的出边 遍历整张图 空间复杂度
直接存边 O(m) O(m) O(nm) O(m)
邻接表 O(*u) O(*u) O(n+m) O(m)
邻接矩阵 O(1) O(n) O(n^2) O(n^2)
链式前向星 O(*u) O(*u) O(n+m) O(m)

备注:n指图中的点数,m指图中边数,u指当前遍历的点,*u指u的出度


图上遍历
深搜 全称是深度优先搜索

所谓深搜,就是每一次向着深度更大的结点前进,特点就是不撞南墙不回头
如果把以上的话写成伪代码,应该是这样的:

DFS(u){//当前点为u
	mark u//对u做一个到达标记,防止重复经过
	for(遍历所有与u连接的点v){
		if(v未被标记过){
			DFS(v)
		}
	}
}

这样更容易用C++把它写出来了,下面以邻接表为例

vector<int> g[N];
bool vis[N];//到达标记
void dfs(int u){
	vis[u]=true;
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		if(!vis[v]) dfs(v);
	}
}

广搜 全称是广度优先搜索,也叫宽度优先搜索
广搜,就是每次先访问同一层的节点,然后再遍历下一层
伪代码:

BFS(x){
	mark x
	将起点x加入列表
	遍历列表的第一个点u,并删掉u
		for(遍历所有与u连接的点v){
			if(v未被标记过){
				加入列表
			}
		}
		
}

那么对于广搜,我们会选择用队列来实现

queue<int> q;
vector<int> g[N];
bool vis[N];
void bfs(int x){
	vis[x]=true;
	q.push(x);
	while(!q.empty()){
		int u=q.top(); q.pop();
		for(int i=0;i<g[u].size();i++){
			int v=g[u][i];
			if(!vis[v]) q.push(v);
		}
	}
}

Floyd

问题模型
Floyd解决的是多源(多个起点)最短路径问题,并且能够求出任意两点间的最短路径。
缺点:复杂度较高(区区3个for而已)
思路
对于这样的一个最短路径问题,我们可以用类似DP的方式来解决
定义\(f[x][y]\)表示x到y的距离
思考一下,\(f[x][y]\)会怎么表示呢?
它可以找一个中转点k,于是\(f[x][y]=f[x][k]+f[k][y]\)
那么我们只需要依次枚举k,x,y,来计算即可
代码:

memset(f,0x3f,sizeof(f));
for(int i=1,u,v,w;i<=n;i++){
	cin>>u>>v>>w;
	f[u][v]=w;
}
for(int k=1;k<=n;k++){
	for(int x=1;x<=n;x++){
		for(int y=1;y<=n;y++){
			f[x][y]=min(f[x][y],f[x][k]+f[k][y]);
		}
	}
}

Dijkstra
问题模型

Dijkstra解决的是单源(单个起点)最短路径问题,能求出起点到任意一点的最短路径。
思路
先给出一个引理:一个点的最短路径必然在其需要经过的中转点的最短路径的基础上得到的
image
图2-1中,要求A->E的最短路径,D为中转点,因为A->D的最短路径为A->B->D,所以A->E的最短路径就一定包含A->B->D
算法实现参考代码

int n;
int cost[MAXN][MAXN];
bool found[MAXN];
int dis[MAXN];
void Dijkstra(int s) {
    memset(found, false, sizeof(found));
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0;
    while(true) {
        int u = -1;
        for(int i=1; i<=n; i++)
            if(!found[i]&&(u==-1||dis[i]<dis[u])) 
                u = i;
        if(u==-1)   return;
        found[u] = true;
        for(int i=1; i<=n; i++) 
//          if(cost[u][i]<INF) 
                dis[i] = min(dis[i], dis[u]+cost[u][i]);
    }
}

Dijkstra堆优化
Dijkstra算法优化原理

当Djikstra未优化时,时间大部分花在查找更新为蓝点的白点上了。
因此可以借助堆(优先队列)来优化下一次更新的白点的过程。
堆的本质是一个二叉树(二叉树的查找最小值为O(1),删除和插入为O(logn))
优化的方式是将最短距离用小根堆进行维护。查找更新的白点的过程时间复杂度为O(logn)
综上时间复杂度可以降为O(nlogn+E),可以近似看为 O(Elogn),这样时间复杂度就大大降低了。
Dijkstra的堆优化辅助工具
Dijkstra的堆优化需要用到priority_queue(堆)和pair
每次需要从白点中挑选一个距离最小的白点,需要知道每个点的最短距离以及点的编号,此时用pair存储。
而优先队列里每次需要先比较距离,因此在放入pair时,距离放在编号前面。

typedef pair<int,int> P;//距离,编号
priority_queue<P,vector<P>,greater<P> > q;//定义一个小根堆,类型是P 优先比较距离

此时就可以对Dijkstra进行堆优化了
pair
头文件

#include<vector>

用法 用来存储一对相关的数据,实现方式是结构体的方式
写法:pair<type1,type2> p;
type1,type2是数据类型,p是变量名
p=pair<type1,type2>(a,b); a是type1类型的,b是type2类型的。
p.first 表示a, p.second 表示b
可以比较大小,优先比较p.first,再比较p.second
pair用起来不是很方便,比较长,这时候可以借助typedef 给类型名起别名

typedef pair<int,int> P;//pair<int,int> 起了一个别名P

即:

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
typedef pair<int,int> P;
P p1,p2; 
int main(){
    p1=P(1,2);
    p2=P(2,3);
    printf("(%d,%d)\n(%d,%d)\n",p1.first,p1.second,p2.first,p2.second);
    if(p1>p2){
        printf("(%d,%d)>(%d,%d)\n",p1.first,p1.second,p2.first,p2.second);
    }
    else{
        printf("(%d,%d)<=(%d,%d)\n",p1.first,p1.second,p2.first,p2.second);
    }
    return 0;
} 

参考代码

typedef pair<int,int> P;//距离,编号
struct edge{
    int to, w;
}; 
vector<edge> g[MAXN];
int dis[MAXN];

void dijkstra(int s){
    memset(dis,0x3f,sizeof(dis));
    //fill(dis,dis+MAXN,INF);
    dis[s]=0;
    q.push(P(0,s));
    while(!q.empty()){
        P t=q.top(); q.pop();
        int u=t.second;//编号 
        if(d[u]<t.first) continue;
        // 小根堆里的队首元素 是最小值,如果满足此条件说明已经被更新过了,就不需要再更新。 
        for(int i=0;i<g[u].size();i++){
            edge e=g[u][i];
            if(dis[e.to]>dis[u]+g[u][i]){
                dis[e.to]=dis[u]+g[u][i];
                q.push(P(dis[e.to],e.to));
            }
        }
    }
}

Prim

最小生成树问题

n个点,以及n个点之间存在若干条权值不同的边,权值表示建这条边的代价。
现在求应该选哪些边,可以使得n个点连通,并且总花费最小。
定理: n个点,n-1条边的无向连通图,一定是树。
在一个图上生成出的树叫做这个图的一个生成树,边的权值之和最小的生成树叫做这个图的最小生成树。
Prim算法
首先把所有点分为两类:蓝点和白点
已经选 入最小生成树中的点: 蓝点
还没有选入最小生成树中的点:白点
初始化随机一点到最小生成树的距离为0,其它点到最小生成树的距离为inf
每次从白点的集合中挑选出距离已有的最小生成树距离最近的白点,加入最小生成树中
直到所有点都被加入最小生成树
Prim算法模板

 int MST_prim(){
    int ans=0;
    memset(dis,0x3f,sizeof(dis));
    memset(found,false,sizeof(found));
    dis[1]=0;
    while(1){
        int u=-1;
        for(int i=0;i<=n;i++){
            if(!found[i]&&(u==-1||dis[i]<dis[u])){
                u=i;
            }
        }
        if(u==-1) break;
        found[u]=true;
        ans+=dis[u];
        for(int i=0;i<=n;i++){
            if(!found[i]&&g[u][i]<dis[i]){
                dis[i]=g[u][i];
            }
        }
    }
    return ans;
}

拓扑排序

拓扑排序算法思想
从DAG图中选择一个没有前驱的顶点并输出
从图中删除该顶点和以该顶点为起点的有向边。
重复1、2,直到当前的DAG为空,或者当前图中不存在有前驱的顶点为止。如果最终图不为空,则必然存在环。
参考代码

int n,m;
vector<int> g[MAXN];
queue<int> q;
int f[MAXN],in[MAXN];

void Topsort(){
    for(int i=1;i<=n;i++){
        if(in[i]==0){
            q.push(i);
        }
    }
    while(!q.empty()){
        int t=q.front();
        cout<<t<<" ";
        q.pop();
        for(int i=0;i<g[t].size();i++){
            int idx=g[t][i];
            if(--in[idx]==0){
                q.push(idx);
            }
        }
    }
}

欧拉路径
  • 若图\(G\)中存在这样一条路径,使得它恰通过\(G\)中每条边一次,则称该路径为欧拉路径。若该路径是一个圈,则称为欧拉回路
  • 我们定义奇点为度为奇数的点,对于存在欧拉路径的图,有以下两个定理:
  • 存在欧拉路径的充要条件:图是连通的,有且只有\(2\)个奇点
  • 存在欧拉回路的充要条件:图是连通的,有\(0\)个奇点
  • 找欧拉路径(回路)的算法是深搜,时间复杂度为深搜的时间复杂度(邻接矩阵:\(O(n^2)\);邻接表:\(O(n+m)\)

参考代码

通过上面两个图,发现深搜时先序记录是不对的,只能后序遍历
代码如下:

#include <iostream>
#include <cstdio>
#include <vector>
#define MAXN 5010
using namespace std;
int n,m,start;//start是起点 
bool g[MAXN][MAXN];//此图用邻接矩阵存储 
int degree[MAXN];//每一个点的度 
int path[MAXN],cnt;//记录路径 
void find(int x){
    for(int i=1;i<=n;i++){
        if(g[x][i]){
            g[x][i]=g[i][x]=false;//该图是无向图 
            find(i);
        }
    }
    path[++cnt]=x;//记录当前路径,后序记录路径 
    //注意:只能后序记录,不能先序记录!先序记录是不对的! 
    //这是因为,如果遇到分叉口,先序遍历的话,一旦走错,就凉了! 
}
int main(){
    scanf("%d %d",&n,&m);//n为点的个数,m为边的数量 
    for(int i=1;i<=m;i++){//输入的时候处理度的问题 
        int x,y;
        scanf("%d %d",&x,&y);
        g[x][y]=g[y][x]=true;
        degree[x]++;
        degree[y]++;
    }
    for(int i=1;i<=n;i++){
        if(degree[i]%2==1){
            start=i;//存在奇点,以这个点为起点 
            break;
        }
    }
    if(start==0)//没有奇点 
        find(1);//从任何一个点开始均可 
    else//有两个奇点 
        find(start);//从一个奇点开始,到另一个奇点结束 
    for(int i=cnt;i>=1;i--){//如果题目要求你按字典序输出,需要逆序输出 
        printf("%d ",path[i]);
    }
    return 0;
}

哈密尔顿回路

参考代码

#include <iostream>
using namespace std;
int n, m, g[105][105], ans[105];
bool vis[105];
bool dfs(int x, int t) {
    if(t>n) {
        if(g[x][1])    return true;
        return false;
    }
    for(int i=1; i<=n; i++) {
        if(g[x][i] && !vis[i]) {
            ans[t] = i;
            vis[i] = true;
            if(dfs(i, t+1))    return true;
            vis[i] = false;
        }
    }
    return false;
}
int main() {
    cin >> n >> m;
    int u, v;
    for(int i=1; i<=m; i++) {
        cin >> u >> v;
        g[u][v] = g[v][u] = 1;
    }
    ans[1] = 1;
    vis[1] = true;
    if(dfs(1, 2)) {
        for(int i=1; i<=n; i++)
            cout << ans[i] << ' ';
        cout << ans[1];
    }
    else    cout << "NO";
    return 0;
} 

Kruscal算法
  1. Kruscal算法将一个连通块当做一个集合
  2. Kruscal首先将所有边按从小到大顺序排序(一般使用快排),并认为每一个点都是孤立的,分属于\(n\)个独立的集合
  3. 然后按顺序从小到大枚举每一条边。如果这条边连接着两个不同的集合,那么久把这条边加入最小生成树,这两个不同的集合就合并成了一个集合;如果这条边连接的两个点属于同一个集合,就跳过
  4. 直到选取了\(n-1\)条边结束

参考代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n,m;
struct edge{
    int u,v,cost;
    bool operator<(const edge &tmp)const{
        if(cost!=tmp.cost)
            return cost<tmp.cost;
        if(u!=tmp.u)
            return u<tmp.u;
        return v<tmp.v;
    }
}e[20010];
int fa[5010];
void init(){
    for(int i=1;i<=n;i++)
        fa[i]=i;
}
int find(int x){
    if(x==fa[x])
        return x;
    return fa[x]=find(fa[x]);
}
bool same(int x,int y){
    return find(x)==find(y);
}
void unite(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y)
        return;
    fa[y]=x;
}
int Kruscal(){
    int MST=0;
    init();
    sort(e+1,e+1+m);//e为边集 
    for(int i=1;i<=m;i++){
        if(same(e[i].u,e[i].v)){
            unite(e[i].u,e[i].v);
            MST+=e[i].cost;
        }
    }
    return MST;
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].cost);
    printf("%d",Kruscal());
    return 0;
}

$Bellman-Ford$算法
  • \(Bellman-Ford\)算法解决的是一般情况下的单源最短路径问题,在这里,边的权值可以为负数
  • \(Bellman-Ford\)算法返回一个布尔值,以表明是否存在一个从原点可以到达的权值为负数的环。
  • 如果存在这样的环,算法将告诉我们不存在解决方案。如果没有这种环存在,算法将给出最短路径和它们的权值
  • 算法执行\(n-1\)次,每次遍历边集,至少会找到一个点,使得新的最短路径比原来的更小

伪代码

for i from 1 to n-1: //算法需要重复执行n-1次
    for j from 1 to m: //遍历每一条边
        if dis[e[j].u]+e[j].w<dis[e[j].v]: //这个if操作叫做松弛
            dis[e[j].v]=dis[e[j].u]+e[j].w
for j from 1 to m: //再次执行一次算法
    if dis[e[j].u]+e[j].w<dis[e[j].v]: //此时所有的点的最短路径都算出来了
        return false //如果有边还能松弛,则有负环
return true //没有负环
  • 通过伪代码可以看出,\(Bellman-Ford\)的时间复杂度为\(O(nm)\),完全劣于\(Dijkstra\)算法的\(O(n^2)\)时间复杂度(也劣于优化的\(O(m \log n)\)时间复杂度)

模板

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
int n,m,st,dis[1010];
struct edge{
    int to,cost;
};
vector<edge> g[1010];
bool Bellman_Ford(int s){
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    for(int i=1;i<=n-1;i++){
        for(int j=1;j<=n;j++){
            for(int k=0;k<g[j].size();k++){
                //两层循环遍历整个邻接表 
                edge e=g[j][k];
                if(dis[j]+e.cost<dis[e.to])
                    dis[e.to]=dis[j]+e.cost;
            }
        }
    }
    for(int j=1;j<=n;j++){
        for(int k=0;k<g[j].size();k++){
            //两层循环遍历整个邻接表 
            edge e=g[j][k];
            if(dis[j]+e.cost<dis[e.to])
                return false;
        }
    }
    return true;
    //时间复杂度为O(nm),劣于Dijkstra 
}
int main(){
    cin>>n>>m>>st;
    for(int i=1;i<=n;i++){
        int u,v,w;
        cin>>u>>v>>w;
        g[u].push_back((edge){v,w});
    }
    if(Bellman_Ford(st)){
        for(int i=1;i<=n;i++)
            cout<<dis[i]<<" ";
    }
    else{
        cout<<"No";
    }
    return 0;
}

$SPFA$
  • \(SPFA\)是\(Bellman-Ford\)算法的一种队列实现,减少了不必要的冗余计算
  • 主要思想初始时将起点加入队列。每次从队列中取出一个元素,并对所有与它相邻的点进行修改,若某个相邻的点修改成功,则将其入队。直到队列为空时算法结束
  • 这个算法,利用了每个店不会更新次数太多的特点发明的
  • \(SPFA\)在形式上和广度优先搜索非常类似,不同的是广度优先搜索中一个点除了队列就不可能重新进入队列,但是\(SPFA\)中一个点可能在出队列之后再次被放入队列,也就是说一个点修稿过其它的点之后,过了一段时间可能会获得更短的路径,于是再次用来修改其它的点,这样反复进行下去
  • 时间复杂度:\(O(k \cdot m)\),\(m\)是边的数量,\(k\)是一个常数(不好分析),平均值为\(2\)

核心代码

struct edge {
    int nxt, w;
};
queue<int> q;
int dis[1005];
bool exist[1005];//判断一个点是否已经加入了队列中 
void SPFA(int s) {
    memset(dis, 0x3f, sizeof(dis));
    memset(exist, false, sizeof(exist));
    dis[s] = 0;
    q.push(s);
    exist[s] = true;
    while(!q.empty()) {
        int h = q.front();
        q.pop();
        exist[h] = false;
        for(int i=0; i<g[h].size(); i++) {
            edge e = g[h][i];
            if(dis[e.nxt]>dis[h]+e.w) {
                dis[e.nxt] = dis[h]+e.w;
                if(!exist[e.nxt]) {
                    q.push(e.nxt);
                    exist[e.nxt] = true;
                }
            }
        }
    }
}

最大流

问题模型
最大流是网络流中的一种。
题目:令G=(V,E),是一个有源汇点的网络,希望在G上找到最大的流量
算法:Ford-Fulkson算法

标签:图论,int,路径,MAXN,年版,2023,push,include,dis
From: https://www.cnblogs.com/hzy-ygkl/p/17586131.html

相关文章

  • 【专题】2023消费电子出海白皮书报告PDF合集分享(附原数据表)
    全文链接:https://tecdat.cn/?p=33393原文出处:拓端数据部落公众号在后疫情时代,全球经济和消费力的增长面临巨大考验。2022年,电脑、手机等产品的市场规模出现了小幅收缩调整。然而,在这样的环境下,各种消费电子的细分领域却展现出了强大的韧性。阅读原文,获取专题报告合集全文,解锁文......
  • 2023.8.89周三:输入带空格的字符串
    1.#include<string>strings;getline(cin,s);2.#include<cstring>#include<stdio.h>chara[1024];gets(a);intlen=strlen(a);//得到数组的实际长度//!!!!!!!!!!!!!!注:cin和getline不能连着用,中间需要加一个cin.ignore;......
  • 高频SQL 50题(基础版): 大的国家 | 2023-08-09
    问题World表:+-------------+---------+|ColumnName|Type|+-------------+---------+|name|varchar||continent|varchar||area|int||population|int||gdp|bigint|+-------------+---------+name是该表......
  • 行业追踪,2023-08-09
    自动复盘2023-08-09凡所有相,皆是虚妄。若见诸相非相,即见如来。k线图是最好的老师,每天持续发布板块的rps排名,追踪板块,板块来开仓,板块去清仓,丢弃自以为是的想法,板块去留让市场来告诉你跟踪板块总结:成交额超过100亿排名靠前,macd柱由绿转红成交量要大于均线有必要给每个行......
  • 2023作物育种与人工智能研讨会
    人工智能推动作物育种新绿色革命......
  • 杭电多校 2023 杂题题解
    打算只写点有意思的题。D1JEasyproblemI注意到\(x_i\)单增,所以一个数被减到负数之后,所有的操作都会将它减到负数,也就等价于乘\(-1\)再相加。使用一棵线段树维护所有数,将这些数分为两种,一种如上,一种是区间减。最终所有数都会变为需要乘\(-1\)再相加的数,于是只要每次精......
  • 2023中国(合肥)场景创新峰会成功举办,全息网御被纳入《合肥市第二批场景能力清单》
    场景作为重要的城市资源,在驱动科技创新、产业发展、城市治理方面发挥着重要作用。近年来,为促进数字技术与实体经济深度融合,加速前沿科技转化落地、吸引全球创新资源集聚,合肥市聚焦“双找”:为产品找场景,为场景找产品,推进全领域、全市域、全流程场景应用创新工作,打造“全域场景应用创......
  • YACS2023年7月乙组
    T1:树的计数注意到,深度为\(2\)的点一定是深度为\(1\)的点的儿子节点,深度为\(3\)的点一定是深度为\(2\)的点的儿子节点.....那么深度为\(i\)的点可以是深度为\(i-1\)的儿子节点,对于此题是一个经典的分步乘法计数原理,把深度为\(2\)的儿子节点确定下来是第一步,深度为......
  • 2023/8/9~2023/8/11 做题
    2023/8/9~2023/8/11做题目录2023/8/9~2023/8/11做题CodeforcesRound121(Div.1)C.FoolsandRoadsCodeforcesRound343(Div.2)C.FamilDoorandBracketsCodeforcesRound880(Div.1)A.k-thequalityCodeforcesRound121(Div.1)C.FoolsandRoads树形dp+......
  • 2023下半年产品经理NPDP认证8月19日正式开班,报名从速
    产品经理国际资格认证NPDP是新产品开发方面的认证,集理论、方法与实践为一体的全方位的知识体系,为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。  【认证机构】 产品开发与管理协会(PDMA)成立于1979年,是全球范围内产品开发与管理专业人士最杰出的倡导者,协助个人、企业......