清北学堂2019.8.10 & 清北学堂2019.8.11 & 清北学堂2019.8.12

Day 5 杨思祺(YOUSIKI)

今天的难度逐渐上升,我也没做什么笔记

开始口胡正解

今天的主要内容是最小生成树,树上倍增和树链剖分

最小生成树

Prim

将所有点分为两个集合,已经和点 1 连通的集合 S、未和点
1 连通的集合 T
计算集合 T 中每个点 u 和集合 S 的距离,d_u=min<u,v>∈E,v∈S{w_u,v }

选取集合 T 中距离 S 最近的点 u,选中对应的边,加入集合 S

重复上面的过程,直到所有点都被加入集合 S

朴素写法时间复杂度较劣,可以采用堆优化至 O((N + M) logN)

Prim 是一个基于贪心的算法,可以采用归纳法和反证法证明其正确性。

首先证明第一条选中的边 e1 一定包含在某最优解方案中

如果最优解中不包含边 e1,则加入 e1 一定会出现环,且环上存在比 e1 大的边,用 e1 替换之答案更优。

假设最优解包含前 k 个选中的边,e1, e2, . . . , ek,则类似地可证明 ek+1 存在于最优解中。

运用归纳法,Prim 算法得到的 n − 1 条边构成最优解

Kruskal

将所有边按照边权从小到大排序# 依次考虑每一条边 < ui, vi >,如果这条边和之前选中的边形成环,则不选中此边;反之,选中此边

当考虑遍所有边后,选中的边一定构成了一棵最小生成树需要并查集的支持,时间复杂度一般认为是 O(M logM)

证明依赖于拟阵的知识。

拟阵

遗传性:若 S 是一个独立集,那么 S 的子集 S′ 是独立集。

遗传性的推论:空集是独立集。

交换性:若 A 和 B 是 S 的两个独立集且 |A| < |B|,那么存在一个元素 x 满足 x < A 且 x ∈ B,使得 A ∪ {x} 是一个独立集

交换性的推论:一个集合的所有极大独立集大小都相同。例:线性无关组、无向图生成森林。

拟阵最优化

拟阵最优化问题:将集合中每个元素赋予一个权值,求权值和最小 (大) 的极大独立集。

拟阵最优化的贪心算法:

维护当前独立集 G,初始为空。将元素按照权值排序,从小到大枚举元素 x,若 G ∪ {x} 是一个独立集,那么就将 x 加入独立集并将 x 的权值累加入答案。最后的结果就是权值和最小的及大独立集。

证明:如果最优解不包含最小元素 x_1,记该集合为 A,创建新的集合B {x_1},利用交换性不断将 A 中元素加入 B 直到 |A| |B|,则有 B 集合为更优解,矛盾。故 x 一定属于最优解。利用数学归纳法,假设已经证明 x_1, x_2, . . . , x_k−1 属于最优解,如果存在最优解不包含 x_k,还是创建新的集合 B {x_1, x_2, . . . , x_k−1, x_k },利用交换性将元素加入 B 得到更优解,矛盾。

例题比较多,就不放了

树上倍增

序列倍增

回忆普通的序列倍增思想,以 ST 表为例。

Fi,j 记录区间 [i, i + 2 j − 1] 内信息(区间和或区间最值) 

Fi,j =merge(Fi,,j−1, Fi+2 j−1,j−1 )

取出区间 [l, r] 答案时,使用若干个 Fi,j 即可

如果求区间最值,那么取 Fl,k 和 Fr−2 k ,k 即可,其中 k ⌊log2(r − l + 1⌋

如果求区间和,可以取 Fl,k1 , Fl+2 k1 ,k2 , Fl+2 k1+2 k2 ,k3 , . . . 即可

树上倍增的主要思想

树上从每个点出发到根结点的链也具有类似的形式

Fi,j 表示点 i 向上走 2 j 步的结点

Fi,0 就是点 i 的父亲节点

Fi,j =FFi,j−1,j−1

如何求解 u 向上移动 k 步是哪个点?

将 k 写作 2 的幂次之和,如 11=2 3 + 2 1 + 2 0。 用 Gi,j 表示 i 向上移动 j 步的结果.

Gu,11=FGu,10,0

Gu,10=FGu,8,1

Gu,8=Fu,3 在 O(logN) 步内完成。

LCA(最近公共祖先)

树上倍增最常见的用处是求解两个点的最近公共祖先。

求解 a 和 b 的最近公共祖先

将 a 和 b 调整到相同高度

判断 a 和 b 是否重合,若重合则该点即为答案

令 a 和 b 一起向上移动尽可能大的距离,保证移动后两点 不重合

此时两点的父亲结点即为答案 单次询问时间复杂度 O(logN)

最近公共祖先的常见求解方法有

1.树上倍增

2.树链剖分

3.DFS 序 +RMQ

向上路径

如何求解从 u 到 v 路径上边权最值?保证 v 是 u 的祖先。

Mi,j 表示点 i 出发向上移动 2 j 步经过边权最值

Mi,0=Wi, fatheri 

Mi,j =merge(Mi,j−1, MFi,j−1,j−1) 在树上倍增向上走时取移动区间最值更新答案即可。

树上路径

记 g=LCA(u, v),则树上从 u 到 v 的路径可以拆分为两段:

从 u 到 g 的路径

从 g 到 v 的路径

如何求解从 u 到 v 路径上的边权和?

将路径拆分为两段向上路径,分别求解其答案再合并。

树链剖分

首先是一些基本的概念

树链:不拐弯的路径

剖分:每个点属于一条链

重儿子:子树大小最大的子结点

重链:从一点出发,一直选择重儿子向下走,走到叶子

轻边:不属于任何一条重链的边

分析:从任意一点 u 走到根结点,经过的重链、轻边个数的量级 是多少?

走一条重链、轻边,子树大小至少翻倍,故易知 O(logN)。

一些记号

dep_u 表示点 u 的深度

fat_u 表示点 u 的父亲节点

top_u 表示点 u 所在重链的顶端结点

dfn_u 表示重儿子优先 DFS 下点 u 的时间戳

end_u 表示重儿子优先 DFS 下点 u 子树最大时间戳

求点 a 和点 b 的最近公共祖先.

记 ta=top_a , tb=top_b

如果 ta=tb,则 a 和 b 在同一条重链,深度较小的为 LCA

如果 dep_ta > dep_tb,那么令 a=fat_ta

如果 dep_ta < dep_tb,那么令 b=fat_tb

将树序列化

树的结构较为复杂,相较而言我们更喜欢序列这样的一维结构, 因为有丰富的数据结构及其它算法可以处理序列上的种种问题。

那么能否将树转化为序列以便维护信息?

如果按照 dfn_u 将树转化为序列,有哪些有用的性质?

子树是序列中连续一段

树上路径由 O(logN) 个区间组成

下放板子题

luogu P4114 Qtree1

#include <iostream> 
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
long long read()
{
    long long a=0,b=1;
    char c=getchar();
    while(!isdigit(c))
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(isdigit(c))
    {
        a=(a<<3)+(a<<1)+(c^48);
        c=getchar();
    }
    return a*b;
}
void out(long long a)
{
    if(a>9)
        out(a/10);
    putchar(a%10+'0');
}
const int maxn=100005;
int n,a[maxn];//a数组表示点与父亲连边的长度
int num,head[maxn];
struct node
{
    int to,nxt,dis;
}edge[maxn<<1];
void add(int u,int v,int d)
{
    edge[num].dis=d;
    edge[num].to=v;
    edge[num].nxt=head[u];
    head[u]=num;
    num++;
}
int f[maxn],sz[maxn],dep[maxn],son[maxn];
void dfs(int u,int fa)
{
    int maxm=0;
    sz[u]=1;
    for(int i=head[u];~i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(v==fa)
            continue;
        f[v]=u;
        dep[v]=dep[u]+1;
        a[v]=edge[i].dis;
        dfs(v,u);
        sz[u]+=sz[v];
        if(sz[v]>maxm)
            maxm=sz[v],son[u]=v;
    }
}
int b[maxn],in[maxn],top[maxn];//b数组表示当前编号所指的点,in数组表示点的编号,top数组表示点所在链的顶端
void dfs(int u,int fa,int topf)
{
    in[u]=++num;
    b[num]=u;
    top[u]=topf;
    if(!son[u])
        return;
    dfs(son[u],u,topf);
    for(int i=head[u];~i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(v==fa||v==son[u])
            continue;
        dfs(v,u,v);
    }
}
#define lc p<<1
#define rc p<<1|1
struct Node
{
    int val;
}t[maxn<<2];
void pushup(int p)
{
    t[p].val=max(t[lc].val,t[rc].val);
}
void build(int p,int l,int r)
{
    if(l==r)
    {
        t[p].val=a[b[l]];
        return;
    }
    int m=l+r>>1;
    build(lc,l,m);
    build(rc,m+1,r);
    pushup(p);
}
void update(int p,int l,int r,int L,int z){
    if(l==r)
    {
        t[p].val=z;
        return;
    }
    int m=l+r>>1;
    if(m>=L)
        update(lc,l,m,L,z);
    else 
        update(rc,m+1,r,L,z);
    pushup(p);
}
int query(int p,int l,int r,int L,int R)
{
    if(l>R||r<L)
        return 0;
    if(L<=l&&r<=R)
        return t[p].val;
    int m=l+r>>1;
    return max(query(lc,l,m,L,R),query(rc,m+1,r,L,R));
}
void solve_1()//从第0条边开始存,每条边存两次,所以输入的第i条边对应的是第2*i-2和第2*i-1条边,谁是儿子改谁
{
    int x=read()*2-2,d=read(),u=edge[x].to,v=edge[x+1].to;
    if(f[v]==u)
        update(1,1,n,in[v],d);
    else
        update(1,1,n,in[u],d);
}
void solve_2()//链上查询
{
    int x=read(),y=read(),ans=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])
            swap(x,y);
        ans=max(ans,query(1,1,n,in[top[x]],in[x]));//由于存的是与父亲的距离,而top的父亲直接取所以此处无需加一
        x=f[top[x]];
    }
    if(dep[x]>dep[y])
        swap(x,y);
    ans=max(ans,query(1,1,n,in[x]+1,in[y]));//但是最后一次存储必须加1否则会多询问一条边
    out(ans);
    puts("");
}
int main()
{
    memset(head,-1,sizeof(head));
    n=read();
    for(int i=1;i<n;i++)
    {
        int x=read(),y=read(),d=read();
        add(x,y,d);
        add(y,x,d);
    }
    num=0;
    dfs(1,-1);
    dfs(1,-1,1);
    build(1,1,n);
    char z[10];
    while(1)
    {
        scanf("%s",z);
        if(z[0]=='D')
            break;
        switch(z[0])
        {
            case 'C':solve_1();break;
            case 'Q':solve_2();break;
        }
    }
    return 0;
}

Day 6 杨思祺(YOUSIKI)

这一天主要讲了有关联通分量的内容,下午考试...(真~爆零)

强连通分量

在有向图中,如果两个点之间存在两个方向的路径,则称两个点 强连通;如果有向图的任何两个顶点都强连通,则称其为强连通图;有向图的极大强连通子图即为强连通分量。

缩点

强连通分量最常见的用途是将能互相到达的点集缩为一个新的点,建立的新图一定是有向无环图。

Tarjan 算法

Tarjan 算法可以在 O(N + M) 的时间复杂度内求解有向图的所 有强连通分量。 首先提取出有向图的一棵搜索树作为基础框架。 # d f nu 为 u 点的 DFS 时间戳 # lowu 为 u 点出发能追溯到的最小时间戳 low_u=min{dfn_u , low_v1 , dfn_v2 } 其中 < u, v1 > 为树枝边,< u, v2 > 为后向边

模板

void tarjan(int x) {
    dfn[x]=low[x]=++tim;
    vis[x]=1;
    s[++top]=x;
    for(int i=head[x];i;i=nxt[i]) {
        int y=to[i];
        if(!dfn[y])
            tarjan(y),low[x]=min(low[x],low[y]);
        else if(vis[y])
            low[x]=min(low[x],dfn[y]);
    }
    if(dfn[x]==low[x]) {
        int y;num++;
        do {
            y=s[top--];
            vis[y]=0;
            color[y]=num;
            size[num]++;
        }while(x!=y);
    }
}

双连通分量

在无向图中,如果无论删去哪条边都不能使得 u 和 v 不联通, 则称 u 和 v 边双连通;

在无向图中,如果无论删去哪个点(非 u 和 v)都不能使得 u 和 v 不联通,则称 u 和 v 点双连通。

割点:删去该点,图分裂为多个连通块。

割边:也叫“桥”,删去该边,图分裂为多个连通块。

点双连通分量

类似地,定义 dfn_u 和 low_u。 如果 v 是 u 的子结点,并且 low_v ≥ dfn_u 则点 u 是割点,删去点u后 v 子树和其它点不连通

每个割点属于多个点双连通分量,非割点只属于一个点双连通分量。

边双连通分量

类似地,定义 dfn_u 和 low_u。 如果 v 是 u 的子结点,并且 low_v > dfn_u 则边 < u, v > 是割边。

每个点属于一个边双连通分量,边双连通分量之间以割边连接。

Day 7 杨思祺(YOUSIKI)

最后一天,讲了有关二分图和差分约束的知识

二分图

二分图:点黑白染色,邻点不同色。

如何判断一个给定的无向图是不是二分图?

从任意一点开始 BFS 或 DFS,起点不妨染色为白

当前在 u 点时,尝试将所有邻点染为不同的颜色

如果邻点已经染色且颜色不符,则不是二分图

二分图的等价条件

无向图是二分图当且仅当其不包含奇环。

二分图匹配

匹配:选取一些边,使得任意两条边没有公共点(每个点至多属于一条边)

最大匹配:选取边数尽可能多

完美匹配:所有点都在匹配中(每个点恰好属于一条边)

匹配边:选中的边

非匹配边:没有选中的边

匹配点:和选中边相连的点

非匹配点:和选中边不相连的点

常常将二分图的点画成两列

二分图最大匹配是一个常见问题.

  匈牙利算法

  网络流

匈牙利算法

理论基础

交错路:从非匹配点出发,依次经过非匹配边、匹配边、非匹配边...

增广路:从非匹配点出发,结束于非匹配点的交错路

增广路定理:任意一个非最大匹配的匹配一定存在增广路

算法思想

初始没有选中的边

寻找增广路

找到增广路则将路径中匹配边和非匹配边对换

找不到增广路则当前为最大匹配

网络流

取额外的两个点作为源点和汇点

源点向左边一列每个点连流量为 1 的边

右边一列每个点向汇点连流量为 1 的边

二分图中每条边从左向右连流量为 1 的边

求最大流即可

最小顶点覆盖 Knoig 定理

二分图最小顶点覆盖数等于其最大匹配数。

最小路径覆盖

给定有向图 G < V, E >。

设 P 是 G 的一个简单路(顶点不相 交)的集合。

如果 V 中每个顶点恰好在 P 的一条路上,则称 P 是 G 的一个路径覆盖。

P 中路径可以从 V 的任何一个顶点开始, 长度也是任意的,特别地,可以为 0.

G 的最小路径覆盖是 G 的 所含路径条数最少的路径覆盖。

最小路径覆盖 = |V| - 二分图最大匹配

二分图:将原图每个点拆分为入点和出点,如果原图存在 u 到 v 的边,则在 u 的出点和 v 的入点间连无向边。

差分约束

差分约束可以确定特定的不等式组是否存在解.

xi1 − xj1 ≤ a1

xi2 − xj2 ≤ a2 

......

为每个变量 xi 建立一个点 pi.

如果要求 xi − xj ≤ a,则建立一条从 pj 到 pi 长度为 a 的边

以任意一点为起点求单源最短路,则一定有 di − dj ≤ a 如果出现负环,则归结出形如 xi − xi < 0 的约束,不等式组无解。

如果没有负环,最短路算法得到的距离数组就是一组合法的解。

转载于:https://www.cnblogs.com/gongcheng456/p/11336628.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值