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 后,会有两种情况。

  1. 由于 lca' 太靠上,正好就在 \(r\) 的祖先链上,于是这段区间加上 \(r\) 之后。

  2. 加上 \(r\) 之后,lca' 被抬升到了距 \(r\) 的祖先链最近的节点上,深度也变浅了。

因为 lca 中最深的是最有可能不在 \(r\) 的祖先链上的点。

因为假设有一个 \(lca1\)(6),\(lca2\)(3),\(r\) 为 \(7\) 号节点如图。
easy LCA

存在 \(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\) 这么小一定有它的道理

直接考虑状压

\[ANS = E[连通块个数] = Σ E[S是一个极大连通块] \]

根据答案的公式,我们设 \(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(A∪B) = bu(A) · bu(B) · Q(A,B) \]

所以

\[Q(A,B) = bu(A ∪ B) /(bu(A) · bu(B)) = bu(A ∪ B) · inv(A) · inv(B) \]

至此求答案所需的东西我们已经全部会求了

考虑一下复杂度

首先,预处理 \(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;
}
posted @ 2026-11-13 11:14  Lonely_Sun  阅读(9)  评论(0)    收藏  举报