2026CSP-S模拟3 题解
T1 不相邻集合
首先,将出现的数存进桶里,如果出现的数是连续的,那么我们选的数一定是从这个连续段的第一个数开始,每隔一个数选一个,那么这个连续段的答案是 \(\lceil \frac{n}{2} \rceil\)。
对于所有情况,出现的数一定是若干连续段,每段贡献都是上述方法求得的,下面只需要考虑新插入一个数对连续段的影响。
1.新建一段。
2.加入一个连续段的开头或者结尾。
3.将两个连续段拼接起来。
分情况维护即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,ans;
int cnt[N];
struct Node
{
int l,r,res;
} node[N];
int main() {
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
if(cnt[x])
{
cout<<ans<<' ';
continue;
}
cnt[x]++;
if(!cnt[x-1] && !cnt[x+1])
{
ans++;
node[x].l=node[x].r=x;
node[x].res=1;
cout<<ans<<' ';
continue;
}
if(cnt[x-1] && cnt[x+1])
{
ans-=node[x-1].res;
ans-=node[x+1].res;
int L=node[x-1].l,R=node[x+1].r;
node[L].r=R;
node[R].l=L;
int now=(R-L)+1;
ans+=ceil(now/2.0);
node[L].res=node[R].res=ceil(now/2.0);
cout<<ans<<' ';
continue;
}
if(cnt[x-1])
{
ans-=node[x-1].res;
int L=node[x-1].l,R=x;
node[L].r=R;
node[R].l=L;
int now=(R-L)+1;
ans+=ceil(now/2.0);
node[L].res=node[R].res=ceil(now/2.0);
cout<<ans<<' ';
continue;
}
if(cnt[x+1])
{
ans-=node[x+1].res;
int L=x,R=node[x+1].r;
node[L].r=R;
node[R].l=L;
int now=(R-L)+1;
ans+=ceil(now/2.0);
node[L].res=node[R].res=ceil(now/2.0);
cout<<ans<<' ';
continue;
}
}
}
T2 线段树
在线段树中找到所有维护区间满足 $ x \le l \le r \le y$ 的节点是简单的,观察到,答案是所有这样节点的子树编号和的和。
考虑如何求出一棵维护的区间长度为 \(k\),根节点编号为 \(x\) 的线段树的编号和,如果可以求解,那么上面的问题是可解的。
设上面的答案为 \(f(x,k)\)
\(f(x,k)=f(2x,\lceil \frac{x}{2} \rceil) + f(2x + 1,\lfloor \frac{x}{2} \rfloor) + 4x + 1\)
上述等式满足一次函数性质,则必然有:
其中 $b=f(0,k) $ ,\(a=f(1,k)-f(0,k)\)
如果直接记忆化搜索能不能过呢?
分析可知,对于一颗线段树同层的节点,至少有三个 \(k\) 相同的节点,对于 \(k\) 相同的节点,其满足的函数 \(ax+b\) 也相同,可以直接带入 \(x\),综上,一棵有 \(n\) 个节点的线段树,最坏情况下需要递归 $ \frac{n\log_{2}{n}}{2^{\log_{2}{n}}} $ 次,复杂度为 \(O(\log^2{n})\)
code
#include<bits/stdc++.h>
#define lid (id<<1)
#define rid (id<<1 | 1)
#define int long long
using namespace std;
const int mod=1e9+7;
map<int,int> resa,resb;
int n,x,y;
int ans;
int calc(int id,int k)
{
if(k==1) return id;
if(!id)
{
if(resb[k]) return resb[k];
int now=(calc(1,k/2)+calc(0,(k+1)>>1))%mod;
resb[k]=now;
return now;
}
if(id==1)
{
if(resa[k]) return resa[k];
int now=(calc(3,k/2)+calc(2,(k+1)>>1)+1)%mod;
resa[k]=now;
return now;
}
int a,b;
if(resa[k]) a=resa[k];
else a=calc(1,k);
if(resb[k]) b=resb[k];
else b=calc(0,k);
return (id%mod*((a-b)%mod+mod)%mod+b)%mod;
}
void build(int id,int l,int r)
{
if(l>y || r<x) return;
if(l>=x && r<=y)
{
ans+=calc(id,r-l+1);
ans%=mod;
return;
}
int mid=l+r>>1;
build(lid,l,mid),build(rid,mid+1,r);
}
void sol()
{
ans=0;
cin>>n>>x>>y;
build(1,1,n);
cout<<ans<<'\n';
}
signed main() {
freopen("segt.in","r",stdin),freopen("segt.out","w",stdout);
int T;
cin>>T;
while(T--) sol();
}
T3 魔法师
题目中给出的式子没办法整体维护,只能考虑分讨。
设魔杖集合为 \(S\),魔咒集合为 \(M\)。
假设 \(a_s+a_m>b_s+b_m\),整理可得 \(b_m-a_m<a_s-b_s\)。
设 \(v=b_m-a_m\) ,\(u=a_s-b_s\)
那么所有 \(u>v\) 的节点的贡献是来自 \(a\) 的。考虑线段树分治:每次钦定 \(u<v\),\(u\) 来自左子树,\(v\) 来自右子树,计算 \(a\) 的答案;钦定 \(u>v\),\(u\) 来自右子树, \(v\) 来自左子树,计算 \(b\) 的答案。然后再分别递归处理 \(u,v\) 同侧的情况即可。
线段树需要维护区间最小的 $ a_s, a_m, b_s , b_m $ 每次单点修改,线段树分治。由于每个叶子节点对应的值(即 \(u\) 或 \(v\))有多个,所以需要每个叶子节点上维护 4 个 set,分别维护存在的值中最小的上述信息。
复杂度 \(O(n\log n)\)
code
#include<bits/stdc++.h>
#define lx (x<<1)
#define rx (x<<1 | 1)
#define int long long
using namespace std;
const int N=5e5+5,inf=0x3f3f3f3f,eps=250001;
struct Data
{
int as,bs,am,bm;
int res;
};
struct node
{
int l,r;
Data data;
} tree[N<<2];
int lst;
int q;
multiset<pair<int,int>> g1[N][2];
multiset<pair<int,int>> g2[N][2];
void pushup(int x)
{
tree[x].data.am=min(tree[lx].data.am,tree[rx].data.am);
tree[x].data.bm=min(tree[lx].data.bm,tree[rx].data.bm);
tree[x].data.as=min(tree[lx].data.as,tree[rx].data.as);
tree[x].data.bs=min(tree[lx].data.bs,tree[rx].data.bs);
int k1,k2;
k1=tree[lx].data.am+tree[rx].data.as;
k2=tree[lx].data.bs+tree[rx].data.bm;
tree[x].data.res=min(k1,k2);
tree[x].data.res=min(tree[x].data.res,tree[lx].data.res);
tree[x].data.res=min(tree[x].data.res,tree[rx].data.res);
}
void build(int x,int l,int r)
{
tree[x].l=l,tree[x].r=r;
if(l==r)
{
tree[x].data={inf,inf,inf,inf,inf};
return;
}
int mid=l+r>>1;
build(lx,l,mid);
build(rx,mid+1,r);
pushup(x);
}
void ins(int x,pair<int,int> d,int type,int id)
{
int l=tree[x].l,r=tree[x].r;
if(l==r)
{
if(type)
{
tree[x].data.am=d.first;
tree[x].data.bm=d.second;
}
else
{
tree[x].data.as=d.first;
tree[x].data.bs=d.second;
}
int k1=tree[x].data.am+tree[x].data.as;
int k2=tree[x].data.bm+tree[x].data.bs;
tree[x].data.res=min(k1,k2);
return;
}
int mid=l+r>>1;
if(id<=mid) ins(lx,d,type,id);
else ins(rx,d,type,id);
pushup(x);
}
signed main() {
freopen("magic.in","r",stdin),freopen("magic.out","w",stdout);
int c;
cin>>q>>c;
build(1,1,N-3);
for(int i=1;i<=q;i++)
{
int opt,t,a,b;
cin>>opt>>t>>a>>b;
if(c) a^=lst,b^=lst;
if(opt==1)
{
if(t==0)
{
int x=a-b+eps;
g1[x][0].insert({a,b});
g2[x][0].insert({b,a});
a=(*(g1[x][0].begin())).first;
b=(*(g2[x][0].begin())).first;
ins(1,{a,b},0,x);
}
if(t==1)
{
int x=b-a+eps;
g1[x][1].insert({a,b});
g2[x][1].insert({b,a});
a=(*(g1[x][1].begin())).first;
b=(*(g2[x][1].begin())).first;
ins(1,{a,b},1,x);
}
}
if(opt==2)
{
if(t==0)
{
int x=a-b+eps;
g1[x][0].erase({a,b});
g2[x][0].erase({b,a});
if(g1[x][0].size())
{
a=(*(g1[x][0].begin())).first;
b=(*(g2[x][0].begin())).first;
ins(1,{a,b},0,x);
} else {
ins(1,{inf,inf},0,x);
}
}
if(t==1)
{
int x=b-a+eps;
g1[x][1].erase({a,b});
g2[x][1].erase({b,a});
if(g1[x][1].size())
{
a=(*(g1[x][1].begin())).first;
b=(*(g2[x][1].begin())).first;
ins(1,{a,b},1,x);
} else {
ins(1,{inf,inf},1,x);
}
}
}
lst=tree[1].data.res;
if(lst==0x3f3f3f3f) lst=0;
std::cout<<lst<<'\n';
}
}
T4 园艺
斜率优化DP
不想写,咕咕咕。

浙公网安备 33010602011771号