静态仙人掌学习笔记

作为北航传统艺能,进行了一手仙人掌的学。

什么是仙人掌?

一个仙人掌要满足:图连通;每一条边最多位于一个简单环。注意,这种定义下的仙人掌允许重边(长度为 $2$ 的环)的存在。

当然,有的仙人掌还满足无重边,这样的仙人掌不存在长度为 $2$ 的环。

仙人掌有关问题通常有两类解决思路:DFS 树;圆方树。DFS 树的解决思路是根据仙人掌仅是在 DFS 树上加了几条链覆盖的性质,本文不讨论;本文重点讨论圆方树。

圆方树?

把仙人掌的每一个环变成一个方点,环中所有边删掉,对应顶点与方点连边;圆圆边不变,就获得了一棵圆方树。

每个点存一下父亲信息,在 tarjan 的时候,对于一个点 $p$ ,如果其连接的子树 $q$ 在一个环内,则对整个环进行处理。光说太抽象,放代码:

点击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using T=modint;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
vector<pair<int,T>>G[N],adj[N];
int dfn[N],low[N],tot;
int vis[N];
pair<pii,T> fa[N];
vector<pair<pii,T>>square_points;
void add_cactus(int u,int v,T w){
adj[u].pb(mp(v,w));
adj[v].pb(mp(u,w));
}
int tarjan(int p,int f){ // return is cactus?
low[p]=dfn[p]=++tot;
vis[p]=1;
int rings=0,i;
for(i=0;i<G[p].size();i++){
auto pr=G[p][i];
auto q=pr.fi;
if(q==f)continue;
if(!dfn[q]){
fa[q]=mp(mp(p,i),pr.se); // 记录的是父亲连来的边,而不是点
if(!tarjan(q,p))return 0;
low[p]=min(low[p],low[q]);
}
else if(vis[q])low[p]=min(dfn[q],low[p]); // dfn[q] 不是 low[q]
if(low[q]>dfn[p])add_cactus(p,q,pr.se); // 圆圆点,直接加入仙人掌
else if(low[q]<dfn[p]&&++rings>1)return 0; // 记录有多少个点是到父亲的环
}
for(i=0;i<G[p].size();i++){
auto pr=G[p][i];
auto q=pr.fi;
if(dfn[q]<=dfn[p]||(fa[q].fi.fi==p&&fa[q].fi.se==i))continue;
square_points.pb(mp(mp(p,q),pr.se));
}
return 1;
}
void prepare_cactus(){
assert(tarjan(1,0));
int i;
for(i=1;i<=n;i++)assert(dfn[i]);
for(auto pr:square_points)
add_square_point(pr.fi.fi,pr.fi.se,pr.se);
}

其中,G 是原图,adj 是圆方图。T 是边权的类型,可以根据具体情况设置。fa 是对每个点存储的,连向父亲的(注意,不是父亲点,而是父亲边),此举的目的是处理带有重边(长度为 $2$ 的环)的仙人掌。

tarjan 有返回值,返回值表示原图是否为仙人掌。如果某个点的孩子里有两个或以上连向父亲的点(low[q]<dfn[p]),那么图不是仙人掌,代码中使用 ring 记录这样的点数。在这样记录的情况下,一个要重点注意的点是,tarjan 时,low[p]=min(low[p],dfn[q]) 不可写成 low[p]=min(low[p],low[q]),否则会影响到仙人掌的判定。

square_points 存储一些二元组 $(rt,q)$,一个二元组代表一个根为 rt,终点为 q 的环。当一个 $p$ 的孩子节点 $q$ 没有存父亲节点信息的时候,这个点是一个以 $p$ 为根的环的终点。如果题目没保证图是仙人掌,需要在判断了整张图是一个仙人掌后,对这些环进行处理,才能保证复杂度。

对于一个环,可以通过沿 $fa$ 一直跳获得上面的所有节点;后缀和存储在每个点或者边上,环的整体信息存在新的方点上,根上不存储信息。下面是一个后缀和记录距离的实现示例,至于到底记录什么,还需要具体题目具体分析:

点击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void add_square_point(int rt,int q,T w){
int cur=q;
T suf_sw=w;
int new_point=n+ ++square_cnt;

do{
dis[cur]=suf_sw;
auto pr=fa[cur];
suf_sw+=pr.se;
cur=pr.fi;
}while(cur!=rt);
dis[new_point]=suf_sw;dis[rt]=0;

cur=q;
do{
add_cactus(cur,new_point,min(suf_sw-dis[cur],dis[cur]));
cur=fa[cur].fi;
}while(cur!=fa[rt].fi);
}

例题

对于仙人掌类型题目,先考虑在树上的情况,再进行环的处理。

  1. 求任意两点 $(u,v)$ 距离(洛谷 P5236 【模板】静态仙人掌

    考虑树的情况,每个节点 $p$ 只要记录一个从根到 $p$ 的距离,询问时查询 LCA 即可。

    对于 LCA 是个圆点的情况,与树情况相同,仅需要考虑 LCA 是方点的情况,此时设两个环上对应点分别为 $A,B$,则答案即为 $dis(A,u)+dis(B,v)+dis(A,B)$,其中 $dis(A,B)$ 通过处理环的时候进行后缀和可以快速求得(是前缀和、后缀和的较小值)。

    点击
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<set>
    #include<cmath>
    #include<vector>
    #include<cassert>
    #include<ctime>
    #include<bitset>
    #include<functional>
    typedef long long ll;
    using namespace std;
    #define pii pair<int,int>
    #define pll pair<ll,ll>
    #define pil pair<int,ll>
    #define pli pair<ll,int>
    #define fi first
    #define se second
    #define mp make_pair
    #define pb push_back
    #define N 40010
    const int mod=998244353;
    namespace cactus{
    using T=int;
    vector<pair<int,T>>G[N],adj[N];
    int dfn[N],low[N],tot;
    int vis[N];
    pair<int,T> fa[N];
    void add_cactus(int u,int v,T w){
    adj[u].pb(mp(v,w));
    adj[v].pb(mp(u,w));
    }
    void add_edge(int u,int v,T w){
    G[u].pb(mp(v,w));
    G[v].pb(mp(u,w));
    }
    int square_cnt=0,n=0;
    // dis from rt to every q, only calculate points in the same ring
    T dis[N];
    T d[N]; // dis from 1 to i
    void init(int _n){
    int i;
    for(i=0;i<=n+square_cnt;i++){
    dfn[i]=low[i]=vis[i]=0;
    G[i].clear();adj[i].clear();
    fa[i]=mp(0,0);
    }
    tot=square_cnt=0;
    n=_n;
    }
    // point square tree
    vector<pair<pii,T>>square_points;
    void add_square_point(int rt,int q,T w){
    int cur=q;
    T suf_sw=w;
    int new_point=n+ ++square_cnt;

    int cnt=1;
    do{
    dis[cur]=suf_sw;
    auto pr=fa[cur];
    suf_sw+=pr.se;
    cur=pr.fi;
    cnt++;
    }while(cur!=rt);
    dis[new_point]=suf_sw;dis[rt]=0;

    cur=q;
    do{
    add_cactus(cur,new_point,min(suf_sw-dis[cur],dis[cur]));
    cur=fa[cur].fi;
    }while(cur!=fa[rt].fi);
    }
    int tarjan(int p,int f){ // return is cactus?
    low[p]=dfn[p]=++tot;
    vis[p]=1;
    int rings=0;
    for(auto pr:G[p]){
    auto q=pr.fi;
    if(q==f)continue;
    if(!dfn[q]){
    fa[q]=mp(p,pr.se);
    if(!tarjan(q,p))return 0;
    low[p]=min(low[p],low[q]);
    }
    else if(vis[q])low[p]=min(dfn[q],low[p]);
    if(low[q]>dfn[p])add_cactus(p,q,pr.se);
    else if(low[q]<dfn[p]&&++rings>1)return 0;
    }
    for(auto pr:G[p]){
    auto q=pr.fi;
    if(dfn[q]<=dfn[p]||fa[q].fi==p)continue;
    square_points.pb(mp(mp(p,q),pr.se));
    }
    return 1;
    }
    // lca
    int lg[N];
    int dep[N];
    int anc[22][N];
    void dfs4lca(int p,int f){
    dep[p]=dep[f]+1;
    anc[0][p]=f;
    int i;
    for(i=1;(1<<i)<=dep[p];i++)anc[i][p]=anc[i-1][anc[i-1][p]];
    for(auto pr:adj[p]){
    int q=pr.fi;
    if(q!=f)d[q]=d[p]+pr.se,dfs4lca(q,p);
    }
    }
    void init_lca(int rt=1){
    d[rt]=dep[rt]=0;
    dfs4lca(rt,0);
    int i;
    for(i=2;i<=n;i++)lg[i]=lg[i-1]+(1<<(lg[i-1]+1)==i);
    }
    pair<pii,int>lca(int x,int y){
    int i;
    assert(x<=n&&y<=n); // not square points
    if(dep[x]<dep[y])swap(x,y);
    while(dep[x]>dep[y])
    x=anc[lg[dep[x]-dep[y]]][x];
    if(x==y)return mp(mp(0,0),x); // not dealing this case!
    for(i=lg[dep[x]];~i;i--)
    if(anc[i][x]!=anc[i][y])
    x=anc[i][x],y=anc[i][y];
    return mp(mp(x,y),anc[0][x]);
    }
    void prepare_cactus(){
    square_points.clear();
    assert(tarjan(1,0));
    for(auto pr:square_points)
    add_square_point(pr.fi.fi,pr.fi.se,pr.se);
    init_lca();
    }
    T query(int u,int v){
    auto pr=lca(u,v);
    int r=pr.se;
    int a=pr.fi.fi,b=pr.fi.se;
    if(r<=n)return d[u]+d[v]-2*d[r];
    else{
    if(dis[a]<dis[b])swap(a,b);
    T ring_len=dis[r];
    T len_ab=dis[a]-dis[b];
    len_ab=min(len_ab,ring_len-len_ab);
    return d[u]-d[a]+d[v]-d[b]+len_ab;
    }
    }
    };
    int main(){
    int i,j,n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    cactus::init(n);
    for(i=1;i<=m;i++){
    int u,v,w;
    scanf("%d%d%d",&u,&v,&w);
    cactus::add_edge(u,v,w);
    }
    cactus::prepare_cactus();
    while(q--){
    int u,v;
    scanf("%d%d",&u,&v);
    printf("%d\n",cactus::query(u,v));
    }
    return 0;
    }

  1. 求每个环的大小(洛谷 P4129 [SHOI2006]仙人掌

    双倍经验,需要写个高精。

    点击
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<set>
    #include<cmath>
    #include<vector>
    #include<cassert>
    #include<ctime>
    #include<bitset>
    #include<functional>
    typedef long long ll;
    using namespace std;
    #define pii pair<int,int>
    #define pll pair<ll,ll>
    #define pil pair<int,ll>
    #define pli pair<ll,int>
    #define fi first
    #define se second
    #define mp make_pair
    #define pb push_back
    #define N 1200010
    const int mod=998244353;
    namespace high{
    struct num{
    int a[N];
    int deg;
    num(int x){
    assert(x>=0&&x<=9);
    a[deg=1]=x;
    }
    void recalc(){
    while(deg&&a[deg]==0)deg--;
    }
    void print(){
    recalc();
    if(!deg)printf("0");
    else for(int i=deg;i>=1;i--)printf("%c",a[i]+'0');
    }
    num& operator*=(const int x){
    int i;
    recalc();
    for(i=1;i<=deg;i++)a[i]*=x;
    for(i=1;i<=deg;i++){
    if(a[i]>=10){
    if(i==deg)a[++deg]=0;
    a[i+1]+=a[i]/10;
    a[i]%=10;
    }
    }
    return *this;
    }
    };
    }
    namespace cactus{
    high::num res(1);

    using T=int;
    vector<pair<int,T>>G[N],adj[N];
    int dfn[N],low[N],tot;
    int vis[N];
    pair<int,T> fa[N];
    void add_cactus(int u,int v,T w){
    adj[u].pb(mp(v,w));
    adj[v].pb(mp(u,w));
    }
    void add_edge(int u,int v,T w){
    G[u].pb(mp(v,w));
    G[v].pb(mp(u,w));
    }
    int square_cnt=0,n=0;
    // dis from rt to every q, only calculate points in the same ring
    T dis[N];
    T d[N]; // dis from 1 to i
    void init(int _n){
    int i;
    for(i=0;i<=n+square_cnt;i++){
    dfn[i]=low[i]=vis[i]=0;
    G[i].clear();adj[i].clear();
    fa[i]=mp(0,0);
    }
    tot=square_cnt=0;
    n=_n;
    }
    // point square tree
    vector<pair<pii,T>>square_points;
    void add_square_point(int rt,int q,T w){
    int cur=q;
    T suf_sw=w;
    int new_point=n+ ++square_cnt;

    int cnt=1;
    do{
    dis[cur]=suf_sw;
    auto pr=fa[cur];
    suf_sw+=pr.se;
    cur=pr.fi;
    cnt++;
    }while(cur!=rt);
    dis[new_point]=suf_sw;dis[rt]=0;
    res*=(cnt+1);

    cur=q;
    do{
    add_cactus(cur,new_point,min(suf_sw-dis[cur],dis[cur]));
    cur=fa[cur].fi;
    }while(cur!=fa[rt].fi);
    }
    int tarjan(int p,int f){ // return is cactus?
    low[p]=dfn[p]=++tot;
    vis[p]=1;
    int rings=0;
    for(auto pr:G[p]){
    auto q=pr.fi;
    if(q==f)continue;
    if(!dfn[q]){
    fa[q]=mp(p,pr.se);
    if(!tarjan(q,p))return 0;
    low[p]=min(low[p],low[q]);
    }
    else if(vis[q])low[p]=min(dfn[q],low[p]);
    if(low[q]>dfn[p])add_cactus(p,q,pr.se);
    else if(low[q]<dfn[p]&&++rings>1)return 0;
    }
    for(auto pr:G[p]){
    auto q=pr.fi;
    if(dfn[q]<=dfn[p]||fa[q].fi==p)continue;
    square_points.pb(mp(mp(p,q),pr.se));
    }
    return 1;
    }
    void calc(){
    int i;
    res=high::num(1);
    square_points.clear();
    if(!tarjan(1,0)){
    printf("0\n");
    return;
    }
    for(auto pr:square_points)
    add_square_point(pr.fi.fi,pr.fi.se,pr.se);
    res.print();
    }
    };
    int main(){
    int T,n,m,i;
    scanf("%d%d",&n,&m);
    cactus::init(n);
    for(i=1;i<=m;i++){
    int k;
    scanf("%d",&k);
    int u,v=-1;
    while(k--){
    scanf("%d",&u);
    if(v!=-1)cactus::add_edge(u,v,0);
    v=u;
    }
    }
    cactus::calc();
    return 0;
    }

  1. 求直径(最远距离两点间的距离,不是最长路!)(洛谷 P4244 [SHOI2008]仙人掌图 II

    这一类树形 DP 问题,在 tarjan 的时候就可以顺便全计算出来。

    考虑树上的情况。设 $f_i$ 表示以 $i$ 为根的子仙人掌最深有多深,对于普通点处理是容易的,$f_i=\max_{j\in son_i} f_j+1$,同时在计算过程中可以更新答案。对于一个环,需要将环上任意两点的贡献对答案进行更新,同时将最深的深度更新到这个环的根上

    考虑环两点 $i,j$,对答案的贡献为 $f_i+f_j+(dep_j-dep_i)=(f_i-dep_i)+f_j+dep_j$,于是将环摊开成两倍,枚举右端点,左端点单调队列维护一下即可。

    点击
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<set>
    #include<cmath>
    #include<vector>
    #include<cassert>
    #include<ctime>
    #include<bitset>
    #include<functional>
    typedef long long ll;
    using namespace std;
    #define pii pair<int,int>
    #define pll pair<ll,ll>
    #define pil pair<int,ll>
    #define pli pair<ll,int>
    #define fi first
    #define se second
    #define mp make_pair
    #define pb push_back
    #define N 100010
    const int mod=998244353;
    namespace cactus{
    using T=int;
    vector<int>G[N],adj[N];
    int dfn[N],low[N],tot;
    int vis[N];
    int fa[N];
    void add_cactus(int u,int v){
    adj[u].pb(v);
    adj[v].pb(u);
    }
    void add_edge(int u,int v){
    G[u].pb(v);
    G[v].pb(u);
    }
    int n=0,square_cnt=0;
    int f[N];
    int diam;
    void add_square_point(int rt,int q){
    int cur=q;
    int new_point=n+ ++square_cnt;
    do{
    add_cactus(cur,new_point);
    cur=fa[cur];
    }while(cur!=fa[rt]);
    }
    void calc_dp(int rt,int p){
    int i,hd=0,tl=0,t=p;
    static int a[N];
    while(t!=rt)a[++tl]=t,t=fa[t];
    a[++tl]=rt;t=p;
    int len=tl;
    while(t!=rt)a[++tl]=t,t=fa[t];

    static int q[N]; // decreasing queue
    int qh=1,qt=0;
    for(i=1;i<=tl;i++){
    while(qh<=qt&&i-q[qh]>len/2)++qh;
    if(qh<=qt)diam=max(diam,i+f[a[i]]+f[a[q[qh]]]-q[qh]);
    while(qh<=qt&&f[a[q[qt]]]-q[qt]<f[a[i]]-i)qt--;
    q[++qt]=i;
    }
    for(i=1;i<len;i++)
    f[rt]=max(f[rt],f[a[i]]+min(i,len-i));
    }
    int tarjan(int p,int F){ // return is cactus?
    low[p]=dfn[p]=++tot;
    vis[p]=1;
    int rings=0;
    for(auto pr:G[p]){
    auto q=pr;
    if(q==F)continue;
    if(!dfn[q]){
    fa[q]=p;
    if(!tarjan(q,p))return 0;
    low[p]=min(low[p],low[q]);
    }
    else if(vis[q])low[p]=min(dfn[q],low[p]);
    if(low[q]>dfn[p]){
    add_cactus(p,q);
    diam=max(diam,f[p]+f[q]+1);
    f[p]=max(f[p],f[q]+1);
    }else if(low[q]<dfn[p]&&++rings>1)return 0;
    }
    for(auto pr:G[p]){
    auto q=pr;
    if(dfn[q]<=dfn[p]||fa[q]==p)continue;
    calc_dp(p,q);
    }
    return 1;
    }
    void init(int _n){
    int i;
    diam=0;
    for(i=0;i<=n+square_cnt;i++){
    dfn[i]=low[i]=vis[i]=0;
    G[i].clear();adj[i].clear();
    fa[i]=0;
    }
    tot=square_cnt=0;
    n=_n;
    }
    void prepare_cactus(){
    assert(tarjan(1,0));
    }
    };
    int main(){
    int i,j,n,m;
    scanf("%d%d",&n,&m);
    cactus::init(n);
    for(i=1;i<=m;i++){
    int u,v=0,k;
    scanf("%d",&k);
    while(k--){
    scanf("%d",&u);
    if(v)cactus::add_edge(u,v);
    v=u;
    }
    }
    cactus::prepare_cactus();
    printf("%d\n",cactus::diam);
    return 0;
    }

  1. 一个正常一些的 DPP3687 [ZJOI2017]仙人掌

    给一个图,问有多少种加边方法,使得最终形成一个无自环无重边的仙人掌。

    假做法:需要保证无重边,于是最开始想了个神秘树形 $DP$:设 $f_{0,p}$ 表示子树都没有边连上来,$f_{1,p}$ 表示子树有一个连到 $p$,$f_{2,p}$ 表示有两个连到 $p$。但是用点表示边很难表示全,有很多边界情况,于是做法假了。

    正解:仙人掌本质还是把一个树用一些互不相交的链覆盖。于是先考虑树的情况,每个点连出 $deg_i$ 条边,其中需要两两配对或者有一些孤立点,对于一个固定度数可以求出方案数 $g_d$,其中 $g$ 是一个显然 $DP$: $g_n=g_{n-1}+g_{n-2}(n-1)$,注意到每一种配对形式都使得环的大小大于三,所以是对的。对于每个点的任意一种配对组合,都是一个合法的仙人掌,于是直接把每个点 $i$ 的 $g_{deg_i}$ 乘起来即为答案。仙人掌上,方点隔离开的各个树组成森林,森林每个树互不影响,乘积即为答案。(所以这个题甚至不需要用圆方树做)

    点击
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<set>
    #include<cmath>
    #include<vector>
    #include<cassert>
    #include<ctime>
    #include<bitset>
    #include<functional>
    typedef long long ll;
    using namespace std;
    #define pii pair<int,int>
    #define pll pair<ll,ll>
    #define pil pair<int,ll>
    #define pli pair<ll,int>
    #define fi first
    #define se second
    #define mp make_pair
    #define pb push_back
    #define N 1000010
    const int mod=998244353;
    namespace cactus{
    using T=int;
    vector<int>G[N],adj[N];
    int dfn[N],low[N],tot;
    int vis[N];
    int fa[N];
    void add_cactus(int u,int v){
    adj[u].pb(v);
    adj[v].pb(u);
    }
    void add_edge(int u,int v){
    G[u].pb(v);
    G[v].pb(u);
    }
    int square_cnt=0,n=0;
    void init(int _n){
    int i;
    for(i=0;i<=n+square_cnt;i++){
    dfn[i]=low[i]=vis[i]=0;
    G[i].clear();adj[i].clear();
    fa[i]=0;
    }
    tot=square_cnt=0;
    n=_n;
    }
    // point square tree
    vector<pii>square_points;
    void add_square_point(int rt,int q){
    int cur=q;
    int new_point=n+ ++square_cnt;
    assert(new_point<N);
    do{
    add_cactus(cur,new_point);
    cur=fa[cur];
    }while(cur!=fa[rt]);
    }
    int tarjan(int p,int f){ // return is cactus?
    low[p]=dfn[p]=++tot;
    vis[p]=1;
    int rings=0;
    for(auto q:G[p]){
    if(q==f)continue;
    if(!dfn[q]){
    fa[q]=p;
    if(!tarjan(q,p))return 0;
    low[p]=min(low[p],low[q]);
    }
    else if(vis[q])low[p]=min(dfn[q],low[p]);
    if(low[q]>dfn[p])add_cactus(p,q);
    else if(low[q]<dfn[p]&&++rings>1)return 0;
    }
    for(auto q:G[p]){
    if(dfn[q]<=dfn[p]||fa[q]==p)continue;
    square_points.pb(mp(p,q));
    }
    return 1;
    }
    ll h[N],g[N];
    void dfs4dp(int p,int f){
    ll prod=1;
    int cnt=0;
    vis[p]=1;
    for(auto q:adj[p]){
    if(q==f||q>n)continue; // square point
    dfs4dp(q,p);
    prod=prod*h[q]%mod;
    cnt++;
    }
    if(!f)h[p]=prod*g[cnt]%mod;
    else h[p]=prod*g[(cnt+1)]%mod;
    }
    ll calc(){
    int i;
    ll res=1;
    square_points.clear();
    if(!tarjan(1,0))return 0;
    for(auto pr:square_points)add_square_point(pr.fi,pr.se);
    for(i=0;i<=n+square_cnt;i++)vis[i]=h[i]=0;
    g[0]=g[1]=1;
    for(i=2;i<=n;i++)g[i]=(g[i-1]+g[i-2]*(i-1)%mod)%mod;
    for(i=1;i<=n;i++)if(!vis[i]){
    dfs4dp(i,0);
    res=res*h[i]%mod;
    }
    return res;
    }
    };
    int main(){
    int T,n,m,i;
    scanf("%d",&T);
    while(T--){
    scanf("%d%d",&n,&m);
    cactus::init(n);
    for(i=0;i<m;i++){
    int u,v;
    scanf("%d%d",&u,&v);
    cactus::add_edge(u,v);
    }
    printf("%lld\n",cactus::calc());
    }
    return 0;
    }

  1. 2019 牛客多校 6I Can They Go to Galar?

    对仙人掌进行染色。点 $1$ 染色,之后进行扩散,边权表示扩散的概率。问期望有多少个点被染色。

    根据期望可加性,求出每个点被染的概率再求和即可。设 $p_i$ 表示 $i$ 被染概率,在树上有 $p_i=p_{f_i}\cdot p_{e(f_i,i)}$,在以 $r$ 为根的边权为 $(e_1,e_2,\dots e_i,e_{i+1},\cdots e_k)$ 的环上有 $p_i=p_r(1-\prod_{j<i}e_j)(1-\prod_{j>i}e_j)$。

    这是比较基础的 $dp$,求出圆方树后 $BFS$ 即可。环上处理的是后缀积,如果不处理前缀积,就要用到除法,为了避免除零,需要对加边进行筛选。

    点击
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<set>
    #include<cmath>
    #include<vector>
    #include<cassert>
    #include<ctime>
    #include<bitset>
    #include<functional>
    typedef long long ll;
    using namespace std;
    #define pii pair<int,int>
    #define pll pair<ll,ll>
    #define pil pair<int,ll>
    #define pli pair<ll,int>
    #define fi first
    #define se second
    #define mp make_pair
    #define pb push_back
    #define N 100010
    const int mod=998244353;
    ll qp(ll a,int p){
    ll ans=1;
    for(;p;p>>=1,a=a*a%mod)if(p&1)ans=ans*a%mod;
    return ans;
    }
    struct modint{
    ll a;
    modint(){a=0;}
    modint(ll x):a(x%mod){}
    ll inv()const{assert(a);return qp(a,mod-2);}
    modint operator*(const modint&x){return modint(a*x.a%mod);}
    modint operator/(const modint&x){return modint(x.inv()*a%mod);}
    modint operator+(const modint&x){return modint((a+x.a)%mod);}
    modint operator-(const modint&x){return modint((a+mod-x.a)%mod);}
    };
    namespace cactus{
    using T=modint;
    vector<pair<int,T>>G[N],adj[N];
    int dfn[N],low[N],tot;
    int vis[N];
    pair<pii,T> fa[N];
    void add_cactus(int u,int v,T w){
    adj[u].pb(mp(v,w));
    adj[v].pb(mp(u,w));
    }
    void add_edge(int u,int v,T w){
    G[u].pb(mp(v,w));
    G[v].pb(mp(u,w));
    }
    int square_cnt=0,n=0;
    T dis[N]; // dis from rt to every q, only calculate points in the same ring
    T d[N]; // dis from 1 to i
    int start[N]; // start of square points
    vector<pair<pii,T>>square_points;
    T prob[N];
    void add_square_point(int rt,int q,T w){
    int cur=q;
    T suf_sw=w;
    int new_point=n+ ++square_cnt;
    start[new_point]=q;

    do{
    dis[cur]=suf_sw;
    auto pr=fa[cur];
    suf_sw=suf_sw*pr.se;
    cur=pr.fi.fi;
    }while(cur!=rt);
    dis[new_point]=suf_sw;dis[rt]=0;

    cur=q;
    do{
    add_cactus(cur,new_point,modint(0));
    cur=fa[cur].fi.fi;
    }while(cur!=fa[rt].fi.fi);
    }
    int tarjan(int p,int f){ // return is cactus?
    low[p]=dfn[p]=++tot;
    vis[p]=1;
    int rings=0,i;
    for(i=0;i<G[p].size();i++){
    auto pr=G[p][i];
    auto q=pr.fi;
    if(q==f)continue;
    if(!dfn[q]){
    fa[q]=mp(mp(p,i),pr.se);
    if(!tarjan(q,p))return 0;
    low[p]=min(low[p],low[q]);
    }
    else if(vis[q])low[p]=min(dfn[q],low[p]);
    if(low[q]>dfn[p])add_cactus(p,q,pr.se);
    else if(low[q]<dfn[p]&&++rings>1)return 0;
    }
    for(i=0;i<G[p].size();i++){
    auto pr=G[p][i];
    auto q=pr.fi;
    if(dfn[q]<=dfn[p]||(fa[q].fi.fi==p&&fa[q].fi.se==i))continue;
    square_points.pb(mp(mp(p,q),pr.se));
    }
    return 1;
    }
    void init(int _n){
    int i;
    square_points.clear();
    for(i=0;i<=n+square_cnt;i++){
    dfn[i]=low[i]=vis[i]=0;
    G[i].clear();adj[i].clear();
    fa[i]=mp(mp(0,0),0);
    }
    tot=square_cnt=0;
    n=_n;
    }
    ll calc(){
    int i;
    assert(tarjan(1,0));
    for(auto pr:square_points)
    add_square_point(pr.fi.fi,pr.fi.se,pr.se);
    queue<int>Q({1});
    for(i=1;i<=n+square_cnt;i++)vis[i]=0,prob[i]=0;
    vis[1]=1;
    prob[1]=1;
    while(!Q.empty()){
    int p=Q.front();Q.pop();
    for(auto pr:adj[p]){
    int q=pr.fi;
    if(vis[q])continue;
    if(q<=n)prob[q]=prob[p]*pr.se,vis[q]=1,Q.push(q);
    else{
    vis[q]=1; // ring
    int t=start[q];
    while(t!=p){
    T one=modint(1);
    prob[t]=prob[p]*(one-(one-dis[t])*(one-dis[q]/dis[t]));
    vis[t]=1;Q.push(t);
    t=fa[t].fi.fi;
    }
    }
    }
    }
    T res=0;
    for(i=1;i<=n;i++)res=res+prob[i];
    return res.a;
    }
    };
    int main(){
    int T,i,n,m,nCas=0;
    scanf("%d",&T);
    while(T--){
    scanf("%d%d",&n,&m);
    cactus::init(n);
    for(i=0;i<m;i++){
    int u,v,a,b;
    scanf("%d%d%d%d",&u,&v,&a,&b);
    if(a)cactus::add_edge(u,v,modint(a*qp(b,mod-2)%mod));
    }
    printf("Case #%d: %d\n",++nCas,cactus::calc());
    }
    return 0;
    }

  1. 2021 牛客多校 5A Away from College

    给一个仙人掌,每次询问三个点 $c,d,l$,问在 $c-d,c-l$ 最短路上,且离 $c$ 最远的点。

    三对点的 $lca$ 必定有两个相同,或三个都相同,于是可以转化成一个环上三个点的问题。每个点存的是距离后缀和,再简单讨论一下就可。此时 LCA 如果是方点,还要记录离两个点最近的圆点,而倍增求 $LCA$ 时会交换 $x,y$ ,需要注意!要记录是否被交换。

    点击
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<set>
    #include<cmath>
    #include<vector>
    #include<cassert>
    #include<ctime>
    #include<bitset>
    #include<functional>
    typedef long long ll;
    using namespace std;
    #define pii pair<int,int>
    #define pll pair<ll,ll>
    #define pil pair<int,ll>
    #define pli pair<ll,int>
    #define fi first
    #define se second
    #define mp make_pair
    #define pb push_back
    #define N 200010
    const int mod=998244353;
    namespace cactus{
    using T=int;
    vector<pair<int,T>>G[N],adj[N];
    int dfn[N],low[N],tot;
    int vis[N];
    pair<int,T> fa[N];
    void add_cactus(int u,int v,T w){
    adj[u].pb(mp(v,w));
    adj[v].pb(mp(u,w));
    }
    void add_edge(int u,int v,T w){
    G[u].pb(mp(v,w));
    G[v].pb(mp(u,w));
    }
    int square_cnt=0,n=0;
    int ring_root[N];
    T dis[N]; // dis from rt to every q, only calculate points in the same ring
    T d[N]; // dis from 1 to i
    vector<pair<pii,T>>square_points;
    void add_square_point(int rt,int q,T w){
    int cur=q;
    T suf_sw=w;
    int new_point=n+ ++square_cnt;

    int cnt=1;
    do{
    dis[cur]=suf_sw;
    auto pr=fa[cur];
    suf_sw+=pr.se;
    cur=pr.fi;
    cnt++;
    }while(cur!=rt);
    dis[new_point]=suf_sw;dis[rt]=0;
    ring_root[new_point]=rt;

    cur=q;
    do{
    add_cactus(cur,new_point,min(suf_sw-dis[cur],dis[cur]));
    cur=fa[cur].fi;
    }while(cur!=fa[rt].fi);
    }
    int tarjan(int p,int f){ // return is cactus?
    low[p]=dfn[p]=++tot;
    vis[p]=1;
    int rings=0;
    for(auto pr:G[p]){
    auto q=pr.fi;
    if(q==f)continue;
    if(!dfn[q]){
    fa[q]=mp(p,pr.se);
    if(!tarjan(q,p))return 0;
    low[p]=min(low[p],low[q]);
    }
    else if(vis[q])low[p]=min(dfn[q],low[p]);
    if(low[q]>dfn[p])add_cactus(p,q,pr.se);
    else if(low[q]<dfn[p]&&++rings>1)return 0;
    }
    for(auto pr:G[p]){
    auto q=pr.fi;
    if(dfn[q]<=dfn[p]||fa[q].fi==p)continue;
    square_points.pb(mp(mp(p,q),pr.se));
    }
    return 1;
    }
    // lca
    int lg[N];
    int dep[N];
    int anc[22][N];
    void dfs4lca(int p,int f){
    dep[p]=dep[f]+1;
    anc[0][p]=f;
    int i;
    for(i=1;(1<<i)<=dep[p];i++)anc[i][p]=anc[i-1][anc[i-1][p]];
    for(auto pr:adj[p]){
    int q=pr.fi;
    if(q!=f)d[q]=d[p]+pr.se,dfs4lca(q,p);
    }
    }
    void init_lca(int rt=1){
    d[rt]=dep[rt]=0;
    dfs4lca(rt,0);
    int i;
    for(i=2;i<=n;i++)lg[i]=lg[i-1]+(1<<(lg[i-1]+1)==i);
    }
    pair<pii,int>lca(int x,int y){
    int i,flag=0;
    assert(x<=n&&y<=n); // not square points
    if(dep[x]<dep[y])swap(x,y),flag=1;
    while(dep[x]>dep[y])
    x=anc[lg[dep[x]-dep[y]]][x];
    if(x==y)return mp(mp(0,0),x); // not dealing this case!
    for(i=lg[dep[x]];~i;i--)
    if(anc[i][x]!=anc[i][y])
    x=anc[i][x],y=anc[i][y];
    if(flag)swap(x,y);
    return mp(mp(x,y),anc[0][x]);
    }
    void init(int _n){
    int i;
    square_points.clear();
    for(i=0;i<=n+square_cnt;i++){
    dfn[i]=low[i]=vis[i]=0;
    G[i].clear();adj[i].clear();
    fa[i]=mp(0,0);
    }
    tot=square_cnt=0;
    n=_n;
    }
    void prepare_cactus(){
    assert(tarjan(1,0));
    for(auto pr:square_points)
    add_square_point(pr.fi.fi,pr.fi.se,pr.se);
    init_lca();
    }
    T query_dis(int u,int v){
    auto pr=lca(u,v);
    int r=pr.se;
    int a=pr.fi.fi,b=pr.fi.se;
    if(r<=n)return d[u]+d[v]-2*d[r];
    else{
    if(dis[a]<dis[b])swap(a,b);
    T ring_len=dis[r];
    T len_ab=dis[a]-dis[b];
    len_ab=min(len_ab,ring_len-len_ab);
    return d[u]-d[a]+d[v]-d[b]+len_ab;
    }
    }
    void query(int c,int d,int l){
    auto cd=lca(c,d),cl=lca(c,l),dl=lca(d,l);
    int cc,dd,ll,ring,x=0;
    int disc,disd,disl;
    if(cd.se==cl.se&&cl.se==dl.se){
    ring=dl.se;
    if(dl.se<=n)x=dl.se;
    else{
    cc=cd.fi.fi;disc=dis[cc];
    dd=dl.fi.fi;disd=dis[dd];
    ll=cl.fi.se;disl=dis[ll];
    }
    }else if(cd.se==cl.se){
    ring=dl.se;
    if(dl.se<=n)x=dl.se;
    else{
    cc=ring_root[dl.se];disc=0;
    dd=dl.fi.fi;disd=dis[dd];
    ll=dl.fi.se;disl=dis[ll];
    }
    }else if(cd.se==dl.se){
    ring=cl.se;
    if(cl.se<=n)x=cl.se;
    else{
    dd=ring_root[cl.se];disd=0;
    cc=cl.fi.fi;disc=dis[cc];
    ll=cl.fi.se;disl=dis[ll];
    }
    }else if(cl.se==dl.se){
    ring=cd.se;
    if(cd.se<=n)x=cd.se;
    else{
    ll=ring_root[cd.se];disl=0;
    cc=cd.fi.fi;disc=dis[cc];
    dd=cd.fi.se;disd=dis[dd];
    }
    }
    if(!x){
    int d1=disd-disc,d2=dis[ring]-d1;
    int d3=disl-disc,d4=dis[ring]-d3;
    if(d1<0)d1+=dis[ring],d2-=dis[ring];
    if(d3<0)d3+=dis[ring],d4-=dis[ring];
    int minlen=min(d1,d2)+min(d3,d4);
    if(minlen==d1+d3)x=(d1<d3)?dd:ll;
    else if(minlen==d2+d4)x=(d2<d4)?dd:ll;
    else x=cc;
    }
    printf("%d %d\n",x,query_dis(x,c));
    }
    };
    int main(){
    int i,j,n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    cactus::init(n);
    for(i=1;i<=m;i++){
    int u,v;
    scanf("%d%d",&u,&v);
    cactus::add_edge(u,v,1);
    }
    cactus::prepare_cactus();
    while(q--){
    int c,d,l;
    scanf("%d%d%d",&c,&d,&l);
    cactus::query(c,d,l);
    }
    return 0;
    }