20261112NOIP模拟赛#24
T3 easy LCA
猫会给你一个长度为 \(n\) 的 \(1\) 到 \(n\) 的排列 \(p\),定义连续子段 \(p[l,r]\) 的权值如下:
\(val[l,r]=depth[lca(pl,pl+1,...,pr)]\),也就是 \(pl,pl+1,...,pr\) 的 lca 的深度。他希望求出所有 \(n(n+1)/2\) 个连续子段的权值和 根节点深度为 \(1\)。
第一行一个正整数 \(n( 1≤n≤6 · 10^5)\)。
接下来 \(n−1\) 行,每行两个正整数 \(ui,vi(1≤ui,vi≤n)\),代表树上的一条边 \((u,v)\)。数据保证输入的是一棵合法的树。
接下来一行包含 \(n\) 个正整数,代表一个 \(1\) 到 \(n\) 的排列 \(p\)。
输入
1 2
2 6
6 3
3 4
6 5
1 2 3 4 5 6
输出
51
做法/思路
\(n\) 太大显然我们不能 \(O(n^2)\) 的做,考虑枚举连续字段的右端点 \(r\) ,此时 \(1\) ~ \(r-1\) 共有 \(r-1\) 个左端点,因为 \(r\) 是共同右端点,所以这 \(r-1\) 个区间都包含 \(r\) , 因为 \(val[l,r]\) 是共同 lca,那么说明这 \(r-1\) 个区间的 lca 都必须再 \(pi\) 到根节点的链上。对于这 \(r-1\) 个不加 \(r\) 的区间,lca 和 \(r\) 取 lca 后,会有两种情况。
-
由于
lca'太靠上,正好就在 \(r\) 的祖先链上,于是这段区间加上 \(r\) 之后。 -
加上 \(r\) 之后,
lca'被抬升到了距 \(r\) 的祖先链最近的节点上,深度也变浅了。
因为 lca 中最深的是最有可能不在 \(r\) 的祖先链上的点。
因为假设有一个 \(lca1\)(6),\(lca2\)(3),\(r\) 为 \(7\) 号节点如图。

存在 \(3\) 为曾经的一个 lca,这说明在 \(r\) 之前有一个 \(3\) 号节点子树内的 \(r'\), \(6\) 也同理。如果是 \(r6\) 先枚举到的,那么当时的最深点为 \(3\),这样就自然地把它抬升到 \(2\) 了,反之如果先枚举 \(r3\),那么 \(6\) 就为最深点,必定被抬升。所以对于当前点 \(r\) ,最深的点 \(t\) 到 \(lca(t,r)\) 之间的点都会被抬升到 \(lca(t,r)\),机智的你发现这些店的深度是单调不升的。于是考虑单调栈维护一个二元组 \({num,lca}\),分别表示个数与 lca。
在枚举 \(r\) 的同时维护这个单调栈,栈里其实是只存了以 \(r\) 结尾的子段的贡献,所以每回都要加到答案里。
单调栈部分代码
st[++tp]= {1ll,p[1]};
ll val=dep[p[1]],num=0;//维护一个 val 表示栈里的总贡献
ans+=val;
for(int i=2; i<=n; ++i) {
int lca=LCA(p[i],(tp>0)?st[tp].lca:p[i]);//只有最深的点与 r 才有可能
num=0;
while(dep[st[tp].lca]>=dep[lca]&&tp>0){//一直弹出栈,更新这部分 lca 改变的子段
val-=st[tp].x*dep[st[tp].lca];//出栈减去贡献
num+=st[tp].x,--tp;
}
val+=num*dep[lca]+dep[p[i]],ans+=val,
st[++tp]= {num,lca},st[++tp]= {1ll,p[i]};
}
while(tp>0) {
val-=st[tp].x*dep[st[tp].lca],
--tp;
}
去掉单调栈代码里就只有一个树剖求 lca 了,所以就不挂了。
T4-scarborough fair
历经了末日的洗礼,小 \(W\) 终于回到了大陆,他决定拜访十年前遇到小 \(C\) 的集市。但是
由于时局动荡,集市中的很多道路已经年久失修,所以有一定的概率会无法通行。
具体来说,集市是一张由 \(n\) 个路口和 \(m\) 条连接路口的无向边组成的图 (由于集市中存在
桥,故不保证是平面图),其中第 \(i\) 条道路连接 \(ui, vi\) 两点,有 \(wi\) 的概率是不能通行的。
小 \(W\) 定义一张图的不方便程度为图中的联通块个数,现在给定集市的地图,小 \(W\) 希望
你能帮他求出这张图的期望不方便程度。
第一行两个正整数 \(n(1 ≤ n ≤ 17),m(1 ≤ m ≤ n·(n−1)/2 )\),分别表示集市中的路口数量和无向边数量。
接下来 \(m\) 行,每行三个正整数 \(ui, vi, pi\),表示一条连接 \(ui, vi\) 的边。设该道路不能通行
的概率为 \(wi =a/b\),那么 \(pi ≡ a · b^−1 (mod 998244353)\)。
数据保证输入的图中没有重边和自环。
输入
3 3
1 2 499122177
2 3 499122177
3 1 499112177
输出
374334134
数据范围
对于 \(15\) % 的数据,满足 \(m ≤ 21\)。
对于另外 \(35\) % 的数据,满足 \(n ≤ 11\)。
对于 \(80\) % 的数据 (包括以上 \(50%\) 的数据),满足 \(n ≤ 14\)。
对于 \(100\) % 的数据,满足 \(n ≤ 17\)。
做法/思路
\(15\) % 直接自己枚举每条边连通不连通,乘上相应的概率就可以了
\(n\) 这么小一定有它的道理
直接考虑状压
根据答案的公式,我们设 \(f_S\) 表示 \(S\) 这个点集里的点为一个连通块的 \(P\)
对于 \(f_S\) 可以化成 \(1-P[S不连通]\)
而 \(S\) 不连通的情况则可以固定 \(S\) 内一个点 \(r\),枚举 \(S\) 的真子集且含有 \(r\) 的子集 \(T\)。
累加上 \(f_T · Q(T,S xorT)\)
\(Q(A,B)\) 代表 \(A,B\) 两个点集中的点没有边相连。
\(bu(A)\) 表示 \(A\) 点集内部完全不连通的 \(P\)
所以
至此求答案所需的东西我们已经全部会求了
考虑一下复杂度
首先,预处理 \(bu,inv\) 大概需要\(O(2^n · (n + log))\)
求 \(f\) ,需要枚举 \(S\) 与 \(T\) ,因为实际上每个点只有三种状态:1.不在 \(S,T\) 2. 在 \(S\) 不在 \(T\) 3. 在 \(S\) 也在 \(T\)
所以是 \(O(3^n)\)
总时间复杂度 \(O(2^n ·(n + log)+ 3^n)\),大约 \(1.3·10^8\) ,一秒可以跑过。
AC代码
#include<bits/stdc++.h>
#define ll long long
#define ac read()
using namespace std;
inline ll read() {
ll x=0,f=1;
int c=getchar();
while(!isdigit(c)) {
f=(c=='-')?-1:1;
c=getchar();
}
while(isdigit(c)) {
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return x*f;
}
const int N=20;
const int N2=((1<<17)+10);
const int mod=998244353;
int n,m;
ll mp[N][N];
ll bu[N2],inv[N2],f[N2];
int V[N2];
/*
bu[S] 代表 S 的点集中完全不炼铜的 P
inv[S] 代表 bu[S] 再 mod 998244353 下的逆元
f[S] 表示 S 的点集 为连通的 P
Q(A,B) : A B之间的边全不炼铜的 P
bu(A U B) = Q(A,B) * bu(A) * bu(B)
Q(A U B) = bu(A U B) / (bu(A) * bu(B))
对于 f[S]的计算,为了防止算重 固定最前面的点为 r
我们可以计算 S 不炼铜的 sum(P)
枚举 r 所在的连通块 T(S 的真子集)
因为 T为 S 真子集 , (S^T n T)==S,所以 T 与 S^T 互斥
P_(r 属于 T,(T 的点集外的点,S 点集内的点) 与 T 内的点完全不炼铜)
= f[T] * Q(T,S^T)
答案 ANS 怎么求 ?
ANS 等价于 连通块个数的期望
E[连通块个数] = sum_S c V {f[S] * Q(S, V^S)}
!!! 注意 :* Q(S,V^S ) 是因为要保证 S 是一个极大炼铜子图
*/
inline ll MOD(ll a) {
return (a+mod)%mod;
}
inline ll qpow(ll a,ll b) {
ll res=1;
while(b) {
if(b&1) res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res;
}
inline ll Q(int A,int B) {
return bu[A|B]*inv[A]%mod*inv[B]%mod;
}
int vis[N],cnt=0;
int main() {
n=ac,m=ac;
// mp[u][v] 表示 u <--> v 这条边的不炼铜的概率 P
// 一开始都不连通,必须初始化成 1
int num=1;
V[0]=1,V[1]=1;
for(int i=2; i<=n; ++i) {
num=num*2,V[num]=i;
}
for(int i=1; i<=n; ++i) {
for(int j=i+1; j<=n; ++j) {
mp[i][j]=mp[j][i]=1;
}
}
for(int i=1; i<=m; ++i) {
int u=ac,v=ac,val=ac;
mp[u][v]=mp[v][u]=val;
}
//预处理 bu inv , 用来求 Q(A,B)
int SS=(1<<n);
bu[0]=1,inv[0]=1;
// inv[0]=qpow(1,mod-2)=1;
// 枚举 S 从 1 开始
for(int S=1; S<SS; ++S) {
int R=S&(-S),SR=S^R,v=V[R];
ll val=bu[SR];
for(int i=1; i<=n; ++i) {
if(i!=v&&((S>>(i-1))&1)) {
val=MOD(val*mp[i][v]);
}
}
bu[S]=val,inv[S]=qpow(bu[S],mod-2);
}
// 对于每个单点 f[S]=1
for(int i=1; i<=n; ++i) f[(1<<(i-1))]=1;
// O(3^n) 预处理 f( )
for(int S=1; S<SS; ++S) { // 枚举 S
if((S&(S-1))==0) continue;
ll val=0;// S 不炼铜的 P
int R=S&(-S),SR=S^R;
for(int T=SR; ; T=(T-1)&SR) {
int TT=T|R;
if(TT!=S) val=(val+f[TT]*Q(TT,S^TT)%mod)%mod;
if(T==0) break;
}
f[S]=(1-val+mod)%mod;
}
--SS;// 代表二进制下的点集 V
ll ans=0;
for(int S=1; S<=SS; ++S) {
ans=(ans+(f[S]*Q(S,SS^S))%mod)%mod;
}
cout<<ans<<"\n";
return 0;
}

浙公网安备 33010602011771号