2026.3.2 模拟赛

https://oj.gxyzh.com/d/hzoj/contest/69a266b0c524e40596207b17

小 Z 爱划分

题解

20 分的 dp 很显然:\(dp_{i}=\sum_{j=0}^{i-1}dp_{j}\times (sum_j\oplus sum_i)^2\)

这道题赛时思考用数据结构优化 dp,但其实位运算用数据结构不好维护(数据结构用于维护最值)。

一般地,位运算使用对每一位进行拆分考虑。

先设一个简单的问题:权值不是平方怎么做?也就是说 \(dp_{i}=\sum_{j=0}^{i-1}dp_{j}\times (sum_j\oplus sum_i)\)

\(sum_i\) 进行拆位,设 \(sum_i=p_{i,0}\times 2^0+p_{i,1}\times 2^1+\dots p_{i,30}\times 2^{30}\)

那么 \(dp_{i}=\sum_{j=0}^{i-1}\sum_{r=0}^{30}dp_{j}\times [p_{j,r}\ne p_{i,r}]\times 2^r\)

如何对平方进行处理?考虑平方展开:\((\sum_{i=1}^n a_i)^2=\sum_{i=1}^n\sum_{j=1}^n a_ia_j\)

那么 \(dp_{i}=\sum_{j=0}^{i-1}\sum_{r=0}^{30}\sum_{s=0}^{30}dp_{j}\times [p_{j,r}\ne p_{i,r}\cup p_{j,s}\ne p_{i,s}]\times 2^{r+s}\)

统计二进制位即可。

代码

#include<iostream>
#define  int  long long
using namespace std;
constexpr int N=2e5+5,p=1e9+7;
int n,a[N],dp[N];
int w[35][35][2][2];
void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],a[i]^=a[i-1];
	for(int i=1;i<=n;i++)dp[i]=0;
	for(int j=0;j<=30;j++)
		for(int k=0;k<=30;k++)w[j][k][0][0]=w[j][k][0][1]=w[j][k][1][0]=w[j][k][1][1]=0;
	for(int j=0;j<=30;j++)
		for(int k=0;k<=30;k++)w[j][k][0][0]++;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=30;j++)
			for(int k=0;k<=30;k++)
				dp[i]=(dp[i]+(1ll<<(k+j))%p*w[j][k][!((a[i]>>j)&1)][!((a[i]>>k)&1)]%p)%p;
		for(int j=0;j<=30;j++)
			for(int k=0;k<=30;k++)
				w[j][k][(a[i]>>j)&1][(a[i]>>k)&1]=(w[j][k][(a[i]>>j)&1][(a[i]>>k)&1]+dp[i])%p;
	}cout<<(dp[n]+p)%p<<'\n';
}signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	int T; cin>>T; while(T--)Solve();
	return 0;
}

小 Z 爱优化

题解

赛时考虑对极差二分,但其实这样依然有 \(max\)\(min\) 两个变量。

应当将 \(min\) 固定,求 \(max\) 的最小值,因为 \(min\) 的取值有 \(O(n)\) 种,因此可以进行枚举。

考虑将 \(min\) 固定,有 \(dp_{i}=\min(\max(dp_{i-1},a_i),\max(dp_{i-2}+a_{i-1}+a_i))\),这里要求 \(a_i\ge min,a_{i-1}+a_i\ge min\) 才能进行转移。

现在优化,注意到这是最值的形式,考虑在线段树上进行 dp。

但是发现线段树节点上只能从子节点进行转移, \(i-1\) 不在区间内怎么办?

不妨多加两个维度,表示 dp 区间是否超出节点区间。

\(dp_{u,0/1,0/1}\) 表示 在线段树节点 \(u\) 上的 dp,左右两端是否超出。

那么有 \(dp_{u,0/1,0/1}=\min(\max(dp_{ls,0/1,0},dp_{rs,0,0/1}),\max(dp_{ls,0/1,1},dp_{rs,1,0/1}))\)

每次枚举时对线段树单点修改,查询时全局查询。

当然,上面的式子还可以从动态 dp 的角度推导:

\[\begin{bmatrix} dp_i\\ dp_{i-1} \end{bmatrix}= \begin{bmatrix} a_i & a_{i-1}+a_i \\ +\infty & +\infty \end{bmatrix} \begin{bmatrix} dp_{i-1} \\ dp_{i-2} \end{bmatrix} \]

用线段树优化。

代码

#include<iostream>
#include<algorithm>
#define  lch  (x<<1)
#define  rch  (x<<1|1)
using namespace std;
constexpr int N=2e5+5;
int n,a[N];
struct C{ int i,val; bool b; }b[N<<1];
struct Segment_Tree{
	int dp[N<<2][2][2];
	void update(int x){
		dp[x][0][0]=min(max(dp[lch][0][0],dp[rch][0][0]),max(dp[lch][0][1],dp[rch][1][0]));
		dp[x][1][0]=min(max(dp[lch][1][0],dp[rch][0][0]),max(dp[lch][1][1],dp[rch][1][0]));
		dp[x][0][1]=min(max(dp[lch][0][0],dp[rch][0][1]),max(dp[lch][0][1],dp[rch][1][1]));
		dp[x][1][1]=min(max(dp[lch][1][0],dp[rch][0][1]),max(dp[lch][1][1],dp[rch][1][1]));
	}void build(int x,int L,int R){
		if(L==R){
			dp[x][0][1]=dp[x][1][0]=dp[x][1][1]=2e9;
			dp[x][0][0]=a[L];
			if(L!=1)dp[x][1][0]=a[L-1]+a[L];
			if(L!=n)dp[x][0][1]=a[L]+a[L+1];
			return;
		}int mid=(L+R)>>1;
		build(lch,L,mid),build(rch,mid+1,R);
		update(x);
	}void modify(int x,int id,int opt,int L,int R){
		if(L==R){
			if(opt==1)dp[x][0][0]=2e9;
			if(opt==2)dp[x][0][1]=2e9;
			if(opt==3)dp[x][1][0]=2e9;
			return;
		}int mid=(L+R)>>1;
		if(id<=mid)modify(lch,id,opt,L,mid);
		else modify(rch,id,opt,mid+1,R);
		update(x);
	}int query(){ return dp[1][0][0]; }
}tr;
void Solve(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)
		b[i].i=i,b[i].val=a[i],b[i].b=0;
	for(int i=1;i<n;i++)
		b[i+n].i=i,b[i+n].val=a[i]+a[i+1],b[i+n].b=1;
	sort(b+1,b+(n<<1),[](C x,C y){ return x.val<y.val; });
	tr.build(1,1,n); int ans=2e9;
	for(int i=1;i<(n<<1);){
		if(tr.query()==2e9)break;
		ans=min(ans,tr.query()-b[i].val);
		int val=b[i].val; 
		while(b[i].val==val){
			if(b[i].b)tr.modify(1,b[i].i,2,1,n),tr.modify(1,b[i].i+1,3,1,n);
			else tr.modify(1,b[i].i,1,1,n); i++;
		}
	}cout<<ans<<'\n';
}signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	int T; cin>>T; while(T--)Solve();
	return 0;
}

小 Z 爱考试

题解

这道题可以显然看出与基环树有关(依赖关系),但难点在于如何将期望转化到图上。

注意到点 \(u\) 只会有两种取值,通过算出两种取值的概率,从而得到期望。

注意到有些取值是一定能取到的,考虑将点进行分类:

  1. \(a_i<a_{b_i}\)\(a_i\) 一定会加上 \(w_i\)
  2. \(a_i\ge a_{b_i}+w_{b_i}\)\(a_i\) 一定不会加上 \(w_i\)
  3. \(a_{b_i}\le a_i<a_{b_i}+w_{b_i}\),需要求出 \(a_{b_i}\) 才能确定

但是不难注意到对于 3 类点,若 \(a_{b_i}\) 能加上,则 \(a_i\) 一定能加上,若 \(a_{b_i}\) 加不上,则 \(a_i\) 一定也加不上,关键在于顺序。而 1 类点、2 类点相当于打破了顺序关系。

建出基环树森林,即 \(i\to b_i\) 的依赖关系。

考虑从一个 3 类点,向前跳边,有如下可能:

  1. 跳了 \(k\) 步,跳到一个 1 类点,则 \(i\) 加上的概率为:\(\frac{C_n^{k}(n-k)!}{n!}=\frac{1}{k!}\)
  2. 若跳到一个 2 类点,则 \(i\) 加上的概率为 \(0\)
  3. 若跳到一个环内,则 \(i\) 加上的概率为 \(0\)

这样 \(O(nq)\) 的暴力便很显然了。

注意到有向上跳这样的行为,又由于图为基环树森林,考虑树剖。

一般对于基环树的处理有两种:

  1. 将基环树树拆成 环+森林
  2. 将基环树看成 树+边

题解采用第一种,本人采用第二种(会简单不少)。

用数据结构进行维护,查询一条链上的 \(dep\) 最接近 \(x\) 的且 \(\le dep_x\) 的点。

这其实就是 \(lower\)_\(bound\) 操作,可以用 线段树/平衡树 维护。

但是我们不难发现一种剪枝:若 \(dep\) 最小的点依然不能满足 \(\le dep_x\),则直接 \(continue\)

因此考虑用 \(set\) 维护,一次查询,会跳 \(O(logn)\) 次重链,\(set\) 单次查询最大值是 \(O(1)\) 的,总共至多需要进行一次 \(lower\)\(bound\),单次 \(lower\)\(bound\)\(O(logn)\) 的。

故总复杂度为 \(O(n+qlogn)\)

代码

#include<iostream>
#include<string.h>
#include<vector>
#include<set>
#define  int  long long
using namespace std;
constexpr int N=2e5+5,p=1e9+7;
int inv[N],fac_inv[N];
void init(){
	inv[0]=fac_inv[0]=1;
	inv[1]=fac_inv[1]=1;
	for(int i=2;i<=2e5;i++)
		inv[i]=1ll*(p-p/i)*inv[p%i]%p,
		fac_inv[i]=1ll*fac_inv[i-1]*inv[i]%p; 
}
int n,m;
int a[N],to[N],w[N],flag[N];
vector<int> v[N],rot; bool vis[N],in[N];
int son[N],siz[N],dep[N],top[N],dfn[N],tot;
struct node{
	int id,val,dep;
	bool operator <(const node &x)const{
		return dep>x.dep;
	}
};
set<node> s[N];
void clear(){
	rot.clear(),tot=0;
	for(int i=1;i<=n;i++)
		son[i]=siz[i]=dep[i]=top[i]=dfn[i]=vis[i]=0;
	for(int i=1;i<=n;i++)v[i].clear(),s[i].clear();
}void get_flag(int x){
	if(a[x]<a[to[x]])flag[x]=1;
	else if(a[to[x]]+w[to[x]]<=a[x])flag[x]=2;
	else flag[x]=3;
}void dfs1(int x){//找根节点(环上的一个点) 
	in[x]=vis[x]=1;
	if(in[to[x]])rot.push_back(to[x]);
	else if(!vis[to[x]])dfs1(to[x]);
	in[x]=0;
}void dfs2(int x,int rot,int f){//重链剖分 
	dep[x]=dep[f]+1,siz[x]=1;
	for(int u:v[x])if(u!=f&&u!=rot){
		dfs2(u,rot,x),siz[x]+=siz[u];
		if(siz[son[x]]<siz[u])son[x]=u;
	}
}void dfs3(int x,int rot,int t){
	top[x]=t,dfn[x]=++tot; if(son[x])dfs3(son[x],rot,t);
	for(int u:v[x])if(u!=son[x]&&u!=rot)dfs3(u,rot,u);
}void modify0(int x){
	if(flag[x]!=3)s[top[x]].erase(node{x,flag[x],dep[x]});
	get_flag(x);
	if(flag[x]!=3)s[top[x]].insert(node{x,flag[x],dep[x]});
}void modify1(int x,int val){
	a[x]=val,modify0(x);
	for(int u:v[x])modify0(u);
}void modify2(int x,int val){
	w[x]=val;
	for(int u:v[x])modify0(u);//只会对儿子产生贡献 
}int get_len(int x){
	int dp=0; 
	while(1){
		if(s[top[x]].empty()){
			dp+=dep[x]-dep[top[x]]+1;
			if(vis[top[x]])break; 
			x=to[top[x]]; continue;
		}node nd=*s[top[x]].rbegin();
		if(nd.dep<=dep[x]){//找到了 
			nd=*s[top[x]].lower_bound(node{x,flag[x],dep[x]});
			return (nd.val==1)?fac_inv[dp+dep[x]-nd.dep+1]:0;
		}dp+=dep[x]-dep[top[x]]+1;
		if(vis[top[x]])break; 
		x=to[top[x]];
	}x=to[top[x]];
	while(1){
		if(s[top[x]].empty()){
			dp+=dep[x]-dep[top[x]]+1;
			if(vis[top[x]])break; 
			x=to[top[x]]; continue;
		}node nd=*s[top[x]].rbegin();
		if(nd.dep<=dep[x]){//找到了 
			nd=*s[top[x]].lower_bound(node{x,flag[x],dep[x]});
			return (nd.val==1)?fac_inv[dp+dep[x]-nd.dep+1]:0;
		}dp+=dep[x]-dep[top[x]]+1;
		if(vis[top[x]])break; x=to[top[x]];
	}return 0;
}int query(int x){
	int y=get_len(x);
	return (a[x]+y*w[x])%p;
}void Solve(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>to[i]>>w[i];
		if(to[i]!=i)v[to[i]].push_back(i);
	}for(int i=1;i<=n;i++)get_flag(i);
	for(int i=1;i<=n;i++)
		if(!vis[i])dfs1(i);
	memset(vis,0,sizeof(vis));
	for(int u:rot)dfs2(u,u,0),dfs3(u,u,u),vis[u]=1;
	for(int i=1;i<=n;i++)if(flag[i]!=3)
		s[top[i]].insert(node{i,flag[i],dep[i]});
	for(int i=1,opt,x,y;i<=m;i++){
		cin>>opt>>x;
		if(opt==1)cin>>y,modify1(x,y);
		if(opt==2)cin>>y,modify2(x,y);
		if(opt==3)cout<<query(x)<<'\n';
	}clear();
}signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	init(); int T; cin>>T; while(T--)Solve();
	return 0;
}
posted @ 2026-03-10 20:14  zhoumengxuan  阅读(2)  评论(0)    收藏  举报