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 的角度推导:
用线段树优化。
代码
#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\) 只会有两种取值,通过算出两种取值的概率,从而得到期望。
注意到有些取值是一定能取到的,考虑将点进行分类:
- \(a_i<a_{b_i}\),\(a_i\) 一定会加上 \(w_i\)
- \(a_i\ge a_{b_i}+w_{b_i}\),\(a_i\) 一定不会加上 \(w_i\)
- \(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 类点,向前跳边,有如下可能:
- 跳了 \(k\) 步,跳到一个 1 类点,则 \(i\) 加上的概率为:\(\frac{C_n^{k}(n-k)!}{n!}=\frac{1}{k!}\)
- 若跳到一个 2 类点,则 \(i\) 加上的概率为 \(0\)
- 若跳到一个环内,则 \(i\) 加上的概率为 \(0\)
这样 \(O(nq)\) 的暴力便很显然了。
注意到有向上跳这样的行为,又由于图为基环树森林,考虑树剖。
一般对于基环树的处理有两种:
- 将基环树树拆成 环+森林
- 将基环树看成 树+边
题解采用第一种,本人采用第二种(会简单不少)。
用数据结构进行维护,查询一条链上的 \(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;
}

浙公网安备 33010602011771号