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++!