【未知来源】导览图

Description

image

Samples

image

Solution

给出的边所构成的图中只有两个联通块。

考虑在一张满足要求的导览图上,那么我们会怎么遍历 \(1\sim n\) 这些点。根据题意,我们会从 \(1\) 出发,走若干条有景点的边到某个点 \(s\),从 \(s\) 沿着一条没有景点的边走到另一个联通块上的点 \(t\),再通过有景点的边游览完 \(t\) 所在的联通块,并回溯到 \(s\),再沿着有景点的边走完 \(1\) 所在的联通块里剩下的点,回溯到 \(1\)。也就是说钦定 \(s\)\(t\) 之后遍历顺序是固定的。

不妨记从 1 开始,按照题中的 dfs 方法,遍历 \(1\) 所在的联通块中的点,得到的 dfs 序列是 \(1 = s_1\dots s_k\),另一个联通块中的点是 \(t_1,\dots t_l\)。自然地,记两个联通块为 \(S\)\(T\)

那么我们钦定在遍历过程中,是\(s_i\) 走向 \(t_j\),考虑统计满足这个条件的合法导览图数量。也就是说统计添加哪些边不会影响游览顺序,记其为 \(m\),然后把 \(2^{m}\) 贡献给答案。这样的边分成 \(3\) 类,连接 \((s_x,s_y)\) 的,连接 \((t_x,t_y)\) 的,连接 \((s_x,t_y)\) 的。

  1. 连接 \((s_x,s_y)\)

    考虑 \(S\) 联通块以 \(1\) 为根的 dfs 树。

    首先没有横叉边,否则在 dfs 序小的点回溯之前可以走这条没有景点的横叉边

    对于返祖边 \((s_p,s_q),p<q\),设 \(s_p\)\(s_q\) 方向的儿子是 \(s_r\),那么如果 \(s_q<s_r\) 则这条边不能连,否则在去 \(s_r\) 之前会去 \(s_q\)。这本质上是统计每个点子树里面有多少个点标号大于自己。

    考虑在 dfs 时将所有点加入树状数组这个操作,那么想获得“每个点子树里面有多少个点标号大于自己” 的信息,可以通过在 dfs 到他时查询一下树状数组里面有几个点大于它,从它回溯时再查询一次,两者相减得到。(不用写主席树了我真的谢谢。)

  2. 连接 \((t_x,t_y)\)

    和“连接 \((s_x,s_y)\) 的” 部分完全一样。由于 T 的 dfs 树形态由 \(t_j\) 决定,所以实现的时候需要写一个换根。换根部分考虑一个边 \((fa,x)\),需要计算除去 \(x\)\(x\) 子树里面的点后,有几个点标号大于 fa,那么用所有点中>fa的点数减去子树里面大于 fa 的点数就行了,后者也可以树状数组。

    \(t_j\) 为根时方案数是 \(\rm dp_{t_j}\),第三部分要用。其实第一部分贡献就是 \(\rm dp_1\)

  3. 连接 \((s_x,t_y)\) 的。

    考虑 \(S\) 联通块以 \(1\) 为根的 dfs 树。设 \(1\sim s_i\) 的根链上的点为 \(1 = s_{i_1}\dots s_{i_t} = s_i\)。如果某个 \(s\) 联通块中不在 \(s_i\) 根链上的点 \(s_a\),如果和 \(T\) 中某个 \(t_b\) 相连:如果 \(a<i\),那么游览顺序会\(s_a\) 走到 \(t_b\) 而不是 \(s_i\) 走到 \(t_b\),不能连;如果 \(a>i\),那么游览过程中会从 \(t_b\) 走到 \(s_a\)经过两条没有景点的边,不能连。于是只有 \(s_i\) 根链上的点能连。

    每个 \(s_{i_j}(j<t)\) 能和谁连呢?考虑 \(s_{i_j}\) 走到 \(s_{i_{j+1}}\) 这步,必须在走 \(s_{i_j}\) 走到某个 \(t_p\) 之前。于是如果 \(t_p>s_{i_{j+1}}\),就有一条 \((s_{i_j},t_p)\) 的边可连可不连。

    \(j=t\) 时,也就是 \(s_i\) 自己,它可以和所有 \(t_p>t_j\)\(t_p\) 连边也可以不连,把 \(t_1\dots t_l\) 从小到大排序得到 \(t'_ 1,\dots t'_ l\),每个 \(t'_ i\) 的贡献是 \(dp_{t'_ i}\times 2^{l-i-1}\)。那么总方案数是 \(\displaystyle \sum_{i=1}^l dp_{t'_ i}2^{l-i-1}\),把 \(2^{l-1}\) 拿出去剩下的只和 \(i\) 有关,直接求和就行了


最后可以参考样例 2,如果联通块 T 中只有 \(1\) 个点,那么 \(s_x,t_y\) 中的点可连可不连,有一个额外的 \(\rm dp_1\) 的贡献。

最后可以参考样例 2,如果联通块 T 中只有 \(1\) 个点,那么 \(s_x,t_y\) 中的点可连可不连,有一个额外的 \(\rm dp_1\) 的贡献。

最后可以参考样例 2,如果联通块 T 中只有 \(1\) 个点,那么 \(s_x,t_y\) 中的点可连可不连,有一个额外的 \(\rm dp_1\) 的贡献。

Code

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
inline int read(){int x; scanf("%lld",&x); return x;}
const int mod=998244353;
inline int mul(int x,int y){return x*y%mod;}
inline int add(int x,int y){return (x+y)%mod;}
inline int del(int x,int y){return (x-y+mod)%mod;}
inline void ckadd(int &x,int y){x=add(x,y);}
inline void ckdel(int &x,int y){x=del(x,y);}
inline void ckmul(int &x,int y){x=mul(x,y);}
inline int ksm(int x,int y){
    int res=1;
    for(;y;y>>=1,x=mul(x,x)) if(y&1) res=mul(res,x);
    return res;
}
const int N=5e5+10;
int n;
vector<int>G[N];
int dsu[N],dp[N];
inline int find(int x){return x==dsu[x]?x:dsu[x]=find(dsu[x]);}
struct Fenwick_Tree{
    int c[N],m;
    inline void init(int n){
        m=n;
        for(int i=1;i<=m;++i) c[i]=0;
    }
    inline void ins(int x,int v){
        for(;x;x-=x&(-x)) c[x]+=v;
    }
    inline int query(int x){
        int res=0;
        for(;x<=m;x+=x&(-x)) res+=c[x];
        return res;
    }
}ft;

int val[N],dp2[N],suf[N],gefa[N],tmp2[N];
inline void dfs1(int x,int fa){
    dp[x]=dp2[x]=1;
    int rec2=ft.query(fa+1);
    ft.ins(x,1);
    int rec1=ft.query(x+1);
    for(auto t:G[x]) if(t!=fa){
        dfs1(t,x);
        ckmul(dp[x],dp[t]);
        ckmul(dp[x],ksm(2,val[t]));
    }
    val[x]=ft.query(x+1)-rec1;
    assert(val[x]>=0);
    if(fa && find(x) != find(1)){
        gefa[x]=suf[fa+1]-(ft.query(fa+1)-rec2);
        assert(gefa[x]>=0);
    }
    return ;
}
inline void dfs2(int x,int fa){
    for(auto t:G[x]) if(t!=fa){
        int vv=dp[x];
        ckmul(vv,ksm(dp[t],mod-2));
        ckmul(vv,ksm(ksm(2,val[t]),mod-2));
        dp2[t]=mul(dp2[x],vv);
        ckmul(dp2[t],ksm(2,gefa[t]));
        dfs2(t,x);
    }
    return ;
}

vector<int> S,T;
int ans;
inline void calc_ans(int x,int fat,int cof){
    ckadd(ans,cof);
    for(auto t:G[x]) if(t!=fat) calc_ans(t,x,mul(cof,ksm(2,suf[t+1])));
    return ;
}

signed main(){
    n=read();
    ft.init(n);
    rep(i,1,n) dsu[i]=i;
    for(int i=1;i<n-1;++i){
        int u=read(),v=read();
        G[u].emplace_back(v);
        G[v].emplace_back(u);
        dsu[find(u)]=find(v);
    }
    for(int i=1;i<=n;++i){
        sort(G[i].begin(),G[i].end());
        if(find(i)==find(1)) S.emplace_back(i);
        else T.emplace_back(i),suf[i] ++;
    }
    for(int i=n;i;--i) suf[i]+=suf[i+1];
    dfs1(T[0],0);
    dfs2(T[0],0);
    dfs1(1,0);
    for(auto x:T) ckmul(dp[x],dp2[x]);
    int sum=0,inv2=(mod+1)/2,pi2=1;
    for(int i=0;i<T.size();++i){
        int val=mul(dp[T[i]],pi2);
        ckadd(sum,val);
        ckmul(pi2,inv2);
    }

    calc_ans(1,0,1);
    int coef1 = ksm(2,T.size()-1);
    int coef2 = dp[1];
    int coef3 = sum;
    ans = mul(ans,mul(coef1,mul(coef3,coef2)));
    if(T.size() == 1) ckadd(ans,dp[1]);
    printf("%lld\n",ans);
    return 0;
}

Review

虽然很多人说这题只有 medium 难度,但是我能独立做出来也是蛮开心了,只是场上因为“没过样例但觉得自己觉得过了样例” 而漏掉了一个 case 没有通过。

posted @ 2024-11-25 19:08  yspm  阅读(8)  评论(0编辑  收藏  举报