2021.10.21 模拟赛题解

T1

\(\dfrac{1}{x}+\dfrac{1}{y}=\dfrac{1}{n!}\) 可得 \((x+y)·n!=xy\),移个项可得 \(xy-x·n!-y·n!=0\),再化简一下可以得到 \((x-n!)(y-n!)=(n!)^2\)。因此符合条件的 \((x,y)\) 对数就是 \((n!)^2\) 的因子个数。

赛时建议打表找规律/cy

T2

假设我们交换的两个数为 \(x,y(x<y)\),那么一个显然的结论是,我们每次会选择将 \([l,r]\) 中的区间排序后,第一个不等于自身的数作为 \(x\),再选择 \([x+1,r]\) 中最小值的位置作为 \(y\)

考虑如何维护之。我们二分 \(x\) 的位置 \(p\),那么 \(x>p\) 当且仅当 \([l,p]\) 中不存在逆序对,并且 \([l,p]\) 最大值小于 \([p+1,r]\) 最小值,使用二分 + 线段树可以做到 2log,使用线段树上二分代替二分+线段树则可以做到 1log。

坑点:将一个区间拆分为线段树上一些小区间时,区间个数上界是 \(2\log n\)​ 而不是 \(\log n\)​,构造:长度 \(262144\)​ 的数组,区间为 \([2,262143]\)​。我刚开始过了大样例,结果赛后 15min 把数据传到洛谷上本来想看看运行效率的,结果 WA 掉了,才发现是有 ub(

T3

首先猜个结论可以发现答案是 \(2\sum\limits_{i=2}^n\min(sum-siz_i,siz_i)\)​,其中 \(siz_i\)​ 表示 \(i\)​ 子树内所有点权值之和,\(sum=\sum\limits_{i=1}^na_i\)​。

接下来思考如何正确处理每个询问。不妨假设我们断掉了 \(x\)​ 与其父亲 \(y\)​ 的边,新加入了 \(p\)​ 与 \(q\)​ 之间的边,其中 \(p\)​ 在 \(x\)​ 子树内而 \(q\)​ 在 \(x\)​ 子树外。那么我们发现,除了 \(x\to p,y\to q\)​ 这两段路径上的边,其余边的贡献都等于原树上对应边的贡献,因此我们可以直接维护一个 \(sumv\)​​ 表示所有边的贡献之和,然后再一遍 DFS 求出根节点到每个点路径上所有边原本的贡献之和,这样即可求出不在 \(x\to p,y\to q\) 路径上的边的贡献之和。

那么如何求 \(x\to p,y\to q\)​​ 这两段的贡献之和呢?记 \(l=\text{LCA}(y,q)\)​​,那么稍微画个图可以发现,删除 \(x\to y\)​ 加入 \(p\to q\)​ 的边这一操作,相当于在 \(x\to p\)​ 下面挂了一个大小为 \(sum-siz_x\)​ 的子树,\(y\to l\)​ 下面挂了一个大小为 \(-siz_x\)​ 的子树,\(l\to q\)​ 下面挂上了一个大小为 \(-siz_x\)​ 的子树。因此我们考虑定义函数 \(f(x,y,ext)\)​ 表示如果在 \(x\)​ 下面挂上一个大小为 \(ext\)​ 的子树后,\(x\to y\)​ 路径上所有边的贡献之和。我们发现 \(x\to y\)​ 路径上所有边的贡献之和随着下端点深度的减小而增大,因此可以倍增找到最浅的满足 \(siz_t+ext\le\lfloor\dfrac{sum}{2}\rfloor\)​ 的点 \(t\),我们设这个点为 \(z\)​,那么 \(x\to z\)​ 路径上所有边贡献都是 \(siz_t+ext\)\(z\to y\) 路径上所有边贡献都是 \(sum-siz_t-ext\),预处处理根到每个点路径上的 \(siz\) 之和。这样要三次倍增,常数略大,赛时也 T 成了 80 分,但是赛后信仰卡了发常就过了(

贴一发代码:

using namespace fastio;
const int MAXN=3e5;
const int LOG_N=19;
int n,qu,a[MAXN+5],hd[MAXN+5],nxt[MAXN*2+5],to[MAXN*2+5],ec=0;
ll sum,lim,siz[MAXN+5],tot[MAXN+5],val[MAXN+5];
int fa[MAXN+5][LOG_N+2],dep[MAXN+5];
int bgt[MAXN+5],edt[MAXN+5],tim=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
void dfs(int x,int f){
	fa[x][0]=f;siz[x]=a[x];bgt[x]=++tim;
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==f) continue;
		dep[y]=dep[x]+1;dfs(y,x);
		siz[x]+=siz[y];tot[x]+=tot[y];
	} val[x]=min(sum-siz[x],siz[x]);
	tot[x]+=val[x];edt[x]=tim;
}
ll calc_sum(int x){return tot[x]-val[x];}
int getlca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=LOG_N;~i;i--) if(dep[x]-(1<<i)>=dep[y]) x=fa[x][i];
	if(x==y) return x;
	for(int i=LOG_N;~i;i--) if(fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
bool is_anc(int x,int y){//whether x is an ancestor of y
	return getlca(x,y)==x;
}
bool in_sub(int x,int y){return bgt[x]<=bgt[y]&&bgt[y]<=edt[x];}
ll sum_val[MAXN+5],sum_siz[MAXN+5];
void dfss(int x,int f){
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==f) continue;
		sum_val[y]=sum_val[x]+val[y];
		sum_siz[y]=sum_siz[x]+siz[y];
		dfss(y,x);
	}
}
ll calc_more(int x,int y,ll ext){//sum of edges on x->y if there exists a subtree of size ext below x
	if(x==y) return 0;
	if(siz[x]+ext>lim){
		return 1ll*(sum-ext)*(dep[x]-dep[y])-(sum_siz[x]-sum_siz[y]);
	} else if(siz[y]+ext<=lim){
		return 1ll*ext*(dep[x]-dep[y])+sum_siz[x]-sum_siz[y];
	} else {
		int cur=x,lg=31-__builtin_clz(dep[x]-dep[y]);
		for(int i=lg;~i;i--){
			if(dep[fa[cur][i]]<=dep[y]) continue;
			if(siz[fa[cur][i]]+ext<=lim) cur=fa[cur][i];
		} cur=fa[cur][0];
		ll res=0;
		res+=1ll*(sum-ext)*(dep[cur]-dep[y])-(sum_siz[cur]-sum_siz[y]);
		res+=1ll*ext*(dep[x]-dep[cur])+sum_siz[x]-sum_siz[cur];
		return res;
	}
}
int main(){
//	freopen("ex_walk6.in","r",stdin);
	freopen("walk.in","r",stdin);
	freopen("walk.out","w",stdout);
	read(n);read(qu);dep[1]=1;
	for(int i=1;i<=n;i++) read(a[i]),sum+=a[i];lim=sum>>1;
	for(int i=1,u,v;i<n;i++) read(u),read(v),adde(u,v),adde(v,u);
	dfs(1,0);dfss(1,0);
	for(int i=1;i<=LOG_N;i++) for(int j=1;j<=n;j++)
		fa[j][i]=fa[fa[j][i-1]][i-1];
	ll ans=0;
	while(qu--){
		int x1,y1,x2,y2;read(x1);read(y1);read(x2);read(y2);
		if(dep[x1]<dep[y1]) swap(x1,y1);
		if(fa[x1][0]!=y1) continue;
		if(in_sub(x1,x2)&&in_sub(x1,y2)) continue;
		if(!in_sub(x1,x2)&&!in_sub(x1,y2)) continue;
		if(!in_sub(x1,x2)) swap(x2,y2);
		ll res=calc_sum(x1);
		res-=sum_val[x2]-sum_val[x1];
		//calculate edges on x2 -> x1
		res+=calc_more(x2,x1,sum-siz[x1]);
		int lc=getlca(y2,y1);
		//calculating edges out of subtree x1 except y1 -> y2
		res+=calc_sum(1);res-=calc_sum(x1);
		res-=sum_val[y1]-sum_val[lc];
		res-=sum_val[y2]-sum_val[lc];
		//calculating edges on y1 -> y2
		res+=calc_more(y1,lc,-siz[x1]);
		res+=calc_more(y2,lc,siz[x1]);
		ans^=(res<<1);
	} printf("%lld\n",ans);
	return 0;
}
/*
4 1
1 1 1 100
1 2
1 3
2 4
1 2 2 3
*/

T4

首先我们设 \(dp_{x,v}\) 表示当前在 \(x\),速度为 \(v\) 到达终点的期望步数,转移就直接枚举可以到达的点乘个概率即可,注意到这个 DP 方程有后效性,因此需高斯消元求解,时间复杂度 \(n^6\),拼个性质 B 的暴力可以拿到 52 分。

考虑优化,注意到当 \(|v|\ge\lceil\sqrt{2n}+1\rceil\)​​ 时肯定已经越过边界了,因此 \(v\)​​ 的范围只需枚举到 \([-\lceil\sqrt{2n}+1\rceil,\lceil\sqrt{2n}+1\rceil]\)​​,这样状态数是 \(n^{1.5}\)​​ 的,总复杂度 \(n^{4.5}\)​​,大约可以拿到 60 分。

如何判定一个点的答案是否是 \(-1\)​​ 呢?官方题解给了个建图+跑 tarjan 的做法,但是事实上不用建出图来,我们考虑在高斯消元过程中,如果一列对应位置中没有非零元素则不管这一列,否则就拿这一行去消所有这一列位置上的元素非零的行。设 \(x\) 表示 \((i,0)\) 表示的行,那么如果在消元得到矩阵中第 \(x\)\(x\) 列的元素为零,或者第 \(x\) 行除了第 \(x\) 列之外存在其余非零元素,那 \(i\) 的答案就是 \(-1\)。这样结合前面的暴力可以拿到 72 分。

考虑最后一步优化。注意到如果在一段时间内 \(v\) 恒大于零,或者恒小于零,那么这段时间内人肯定处于不断向右走或者不断向右走的过程,因此我们考虑求出 \(f_{x,y}\) 表示当前位于位置 \(x\),速度为 \(0\),下一次速度为 \(0\) 时在 \(y\) 的概率,再设 \(g_{x,y}\) 表示当前位于位置 \(x\),速度为 \(0\),下一次速度为 \(0\) 时在 \(y\) 时需要走的期望步数,特别地,如果 \(y=n+1\),则 \(f_{x,y}\) 表示从 \((x,0)\) 开始不经过任何速度为 \(0\) 的时刻到达右边界的概率,如果 \(y=0\),则则 \(f_{x,y}\) 表示从 \((x,0)\) 开始不经过任何速度为 \(0\) 的时刻到达左边界的概率,\(g_{x,0},g_{x,n+1}\) 的定义类似——由于当 \(v\) 恒大于 \(0\) 时图是一个拓扑图,恒小于 \(0\) 时也同理,因此 \(f,g\) 可以左右各扫一遍在 \(n^{2.5}\)​ 的时间内求出。这样我们可以得到 DP 方程 \(dp_{x,0}=\sum\limits_{y\ne x}(f_{x,y}dp_{y,0}+g_{x,y})+g_{x,n+1}+g_{x,0}\),这样只用对 \(v=0\) 的点高斯消元,时间复杂度 \(\mathcal O(n^3)\)


祝大家 CSP-S rp++!

posted @ 2021-10-22 08:39  tzc_wk  阅读(12)  评论(1)    收藏  举报