【未知来源】导览图
Description
Samples
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)\) 的。
-
连接 \((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 到他时查询一下树状数组里面有几个点大于它,从它回溯时再查询一次,两者相减得到。
(不用写主席树了我真的谢谢。) -
连接 \((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\)
-
连接 \((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 没有通过。