【未知来源】导览图
Description
Samples
Solution
给出的边所构成的图中只有两个联通块。
考虑在一张满足要求的导览图上,我们会怎么遍历 这些点。根据题意,我们会从 出发,走若干条有景点的边到某个点 ,从 沿着一条没有景点的边走到另一个联通块上的点 ,再通过有景点的边游览完 所在的联通块,并回溯到 ,再沿着有景点的边走完 所在的联通块里剩下的点,回溯到 。也就是说钦定 和 之后遍历顺序是固定的。
不妨记从 1 开始,按照题中的 dfs 方法,遍历 所在的联通块中的点,得到的 dfs 序列是 ,另一个联通块中的点是 。自然地,记两个联通块为 和 。
那么我们钦定在遍历过程中,是从 走向 ,考虑统计满足这个条件的合法导览图数量。也就是说统计添加哪些边不会影响游览顺序,记其为 ,然后把 贡献给答案。这样的边分成 类,连接 的,连接 的,连接 的。
-
连接 的
考虑 联通块以 为根的 dfs 树。
首先没有横叉边,否则在 dfs 序小的点回溯之前可以走这条没有景点的横叉边。
对于返祖边 ,设 在 方向的儿子是 ,那么如果 则这条边不能连,否则在去 之前会去 。这本质上是统计每个点子树里面有多少个点标号大于自己。
考虑在 dfs 时将所有点加入树状数组这个操作,那么想获得“每个点子树里面有多少个点标号大于自己” 的信息,可以通过在 dfs 到他时查询一下树状数组里面有几个点大于它,从它回溯时再查询一次,两者相减得到。
(不用写主席树了我真的谢谢。) -
连接 的
和“连接 的” 部分完全一样。由于 T 的 dfs 树形态由 决定,所以实现的时候需要写一个换根。换根部分考虑一个边 ,需要计算除去 和 子树里面的点后,有几个点标号大于 fa,那么用所有点中>fa的点数 的减去子树里面大于 fa 的点数就行了,后者也可以树状数组。
记 为根时方案数是 ,第三部分要用。其实第一部分贡献就是
-
连接 的。
考虑 联通块以 为根的 dfs 树。设 的根链上的点为 。如果某个 联通块中不在 根链上的点 ,如果和 中某个 相连:如果 ,那么游览顺序会从 走到 而不是 走到 ,不能连;如果 ,那么游览过程中会从 走到 ,经过两条没有景点的边,不能连。于是只有 根链上的点能连。
每个 能和谁连呢?考虑 走到 这步,必须在走 走到某个 之前。于是如果 ,就有一条 的边可连可不连。
时,也就是 自己,它可以和所有 的 连边也可以不连,把 从小到大排序得到 ,每个 的贡献是 。那么总方案数是 ,把 拿出去剩下的只和 有关,直接求和就行了
最后可以参考样例 2,如果联通块 T 中只有 个点,那么 中的点可连可不连,有一个额外的 的贡献。
最后可以参考样例 2,如果联通块 T 中只有 个点,那么 中的点可连可不连,有一个额外的 的贡献。
最后可以参考样例 2,如果联通块 T 中只有 个点,那么 中的点可连可不连,有一个额外的 的贡献。
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 没有通过。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律