【未知来源】导览图

Description

image

Samples

image

Solution

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

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

不妨记从 1 开始,按照题中的 dfs 方法,遍历 1 所在的联通块中的点,得到的 dfs 序列是 1=s1sk,另一个联通块中的点是 t1,tl。自然地,记两个联通块为 ST

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

  1. 连接 (sx,sy)

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

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

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

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

  2. 连接 (tx,ty)

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

    tj 为根时方案数是 dptj,第三部分要用。其实第一部分贡献就是 dp1

  3. 连接 (sx,ty) 的。

    考虑 S 联通块以 1 为根的 dfs 树。设 1si 的根链上的点为 1=si1sit=si。如果某个 s 联通块中不在 si 根链上的点 sa,如果和 T 中某个 tb 相连:如果 a<i,那么游览顺序会sa 走到 tb 而不是 si 走到 tb,不能连;如果 a>i,那么游览过程中会从 tb 走到 sa经过两条没有景点的边,不能连。于是只有 si 根链上的点能连。

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

    j=t 时,也就是 si 自己,它可以和所有 tp>tjtp 连边也可以不连,把 t1tl 从小到大排序得到 t1,tl,每个 ti 的贡献是 dpti×2li1。那么总方案数是 i=1ldpti2li1,把 2l1 拿出去剩下的只和 i 有关,直接求和就行了


最后可以参考样例 2,如果联通块 T 中只有 1 个点,那么 sx,ty 中的点可连可不连,有一个额外的 dp1 的贡献。

最后可以参考样例 2,如果联通块 T 中只有 1 个点,那么 sx,ty 中的点可连可不连,有一个额外的 dp1 的贡献。

最后可以参考样例 2,如果联通块 T 中只有 1 个点,那么 sx,ty 中的点可连可不连,有一个额外的 dp1 的贡献。

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 @   没学完四大礼包不改名  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示