2025.7.26 CSP-S模拟赛26

先咕着=

再不写补不完了啊喂

T1 集合

赛时非常脑瘫没有调出来,其实就是非常简单的权值线段树维护最长连续段,和山海经差不多,维护线段树上每个点左边界开始的最长段,右边界开始的最长段和中间的最长段即可,代码放下面;

#include<bits/stdc++.h>
using namespace std;
#define ls id<<1
#define rs id<<1|1
const int MAXN=2e5+10;
int n,k,a[MAXN];
int nl,nr;
long long ans;
struct tree{
	int l,r,len,sum;
	int lmax,rmax,midmax;
}tr[MAXN<<2];
void build(int id,int l,int r){
	tr[id].l=l,tr[id].r=r,tr[id].len=r-l+1;
	tr[id].sum=tr[id].lmax=tr[id].midmax=tr[id].rmax=0;
	if(l==r){
		return ;
	}
	int mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	return ;
}
void add(int id,int l,int r,int x,int val){
	if(l==r){
		tr[id].sum+=val;
		if(tr[id].sum) tr[id].lmax=tr[id].rmax=tr[id].midmax=1;
		else tr[id].lmax=tr[id].rmax=tr[id].midmax=0;
		return ;
	}
	int mid=l+r>>1;
	if(x<=mid) add(ls,l,mid,x,val);
	else add(rs,mid+1,r,x,val);
	tr[id].sum=tr[ls].sum+tr[rs].sum;
	tr[id].lmax=tr[ls].lmax,tr[id].rmax=tr[rs].rmax;
	tr[id].midmax=tr[ls].rmax+tr[rs].lmax;
	if(tr[ls].midmax==tr[ls].len) tr[id].lmax=max(tr[id].lmax,tr[ls].len+tr[rs].lmax);
	if(tr[rs].midmax==tr[rs].len) tr[id].rmax=max(tr[id].rmax,tr[rs].len+tr[ls].rmax);
	tr[id].midmax=max(tr[id].midmax,max(tr[ls].midmax,tr[rs].midmax));
	return ;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	nl=1,nr=0;
	build(1,1,n);
	for(;nr<n;){
		add(1,1,n,a[++nr],1);
		int now=max(tr[1].lmax,max(tr[1].midmax,tr[1].rmax));
		while(nl<nr&&now>k){
			add(1,1,n,a[nl++],-1);
			now=max(tr[1].lmax,max(tr[1].midmax,tr[1].rmax));
		}
		ans+=nr-nl+1;
	}
	cout<<ans;
	return 0;
}

T2 查后序列

题解说这是签到题,看来这场比赛只有五个人成功签到 (悲

我们维护一个序列,对于每次将队列删空,可以理解为多测;

pop操作较好计算,我们维护队列最大值 \(mx\) ,和当前队列中除了最大值之外的和的期望(记 \(f_i\) 表示第 \(i\) 步操作结束后队列内数的期望和,\(p_j\) 表示第 \(j\) 步删除操作的分母,即 \(cnt-1\) 关于 \(mod\) 的逆元),和数量总数 \(cnt\) ,则 \(ans_i=f_{i-1}*p{j}\)

对于push操作,维护每个数开始可以被删除的位置(若它在插入时不是最大值,他后面也不可能成为最大值,那么插入操作就是它的起始删除位置。否则找到后面插入的第一个比他大的数的位置或队列只剩一个数的位置,就是起始删除位置),我们记 \(qz_j\) 表示序列中数在第 \(j\) 步删除操作中没有被删除的概率,即 \(qz_j=qz_{j-1}*(1-p_j+mod)\) ,特别的,这个东西最小不能小于1;设 \(s_j\) 表示在第\(j\) 步被删除的概率,即 \(s_j=a_j*p_j*qz_{j-1}%mod\) ,其中 \(a_j\) 表示第 \(j\) 步删除操作在所有 \(n\) 个操作中对应的编号,在记录一个 \(qzs_i\) 表示 \(s\) 的前缀;

然后就非常简单了,每个数的期望弹出操作就是从它可以弹出的操作开始,到不可以弹出的操作结束,这之间每一步弹出的期望和,因为前缀数组记录理应从1开始,在具体计算时要在前面补上一个除数才行;

具体的看代码:

#include<bits/stdc++.h>
using namespace std;
#define mod 998244353
const int MAXN=1e6+10;
int cnt1,cnt2;
int n,mx,sum;
int a[MAXN];
long long p[MAXN],f[MAXN];
long long ans[MAXN],val[MAXN];
long long qpow(long long a,long long b){
	long long res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
queue<int> q;
int qmax;
int st[MAXN],ed[MAXN];
long long s[MAXN],g[MAXN],qz[MAXN];
long long qzs[MAXN];
int hs=-1;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		int opt;cin>>opt;
		if(!opt){
			++hs;
			cin>>val[i];
			if(val[i]<=mx) f[i]=(f[i-1]+val[i])%mod,st[i]=cnt2+1,q.push(i);//不为最大值,就永远不可以成为序列最大值,后面的每次修改都可能将其删除
			else{
				f[i]=(f[i-1]+mx)%mod,mx=val[i];
				st[qmax]=cnt2+1;//之前的最大值是没有更新过开始端点的,进入了新的最大值,就要更新数列中原本最大值的开始端点
				if(qmax) q.push(qmax);
				qmax=i;
			}
		}
		else{
			++cnt2;
			if(hs){
				p[cnt2]=qpow(hs,mod-2);
				a[cnt2]=i;
				ans[i]=p[cnt2]*f[i-1]%mod;
				f[i]=(f[i-1]-ans[i]+mod)%mod;
				
			}
			else{
				p[cnt2]=1;
				a[cnt2]=i;
				ans[i]=mx;
				ans[qmax]=i;
				mx=0,qmax=0;
				f[i]=0;
			}
			--hs;
			if(!hs){
				while(!q.empty()){
					int x=q.front();q.pop();
					ed[x]=cnt2;
				}
			}
		}
	}
	while(!q.empty()){
		int x=q.front();q.pop();
		ed[x]=cnt2;
	}
	g[0]=1;
	for(int i=1;i<=cnt2;i++){
		if(p[i]==1) g[i]=1;
		else g[i]=g[i-1]*qpow((1-p[i]+mod)%mod,mod-2)%mod;
	}
	qz[0]=1;
	for(int i=1;i<=cnt2;i++){
		qz[i]=qz[i-1]*((1+mod-p[i])%mod)%mod;
		qz[i]=max(qz[i],1ll);
	}
	for(int i=1;i<=cnt2;i++){
		s[i]=a[i]*p[i]%mod*qz[i-1]%mod;
		qzs[i]=qzs[i-1]+s[i];
		qzs[i]%=mod;
	}
	for(int i=1;i<=n;i++){
		if(ans[i]||!val[i]||!st[i]||!ed[i]||ed[i]<st[i]||ed[i]>cnt2) continue;
		ans[i]=g[st[i]-1]*((qzs[ed[i]]-qzs[st[i]-1]+mod)%mod)%mod;
	}
	for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
		
	return 0;
}

T3 蛋糕

我们可以发现,对于一次操作,要么删完若干行(即删到最低列高度),要么删完一列(这个还是比较好证明的)。那么对于每一种形状的蛋糕,我们都可以把它按照如上两种方法划分去求最小价值,可以采用分治思想。

需要预处理出 \(maxn_{i,j}\) 表示第 \(i\) 列到 \(j\) 列中的最大高度,\(minn_{i,j}\) 同理;

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e3+10;
int n,a[MAXN];
int minn[MAXN][MAXN],maxn[MAXN][MAXN];
map<unsigned long long,long long> mp;
unsigned long long p=13331;
unsigned long long hsh(int l,int r,int k){
	return (unsigned long long)l*p*p+(unsigned long long)r*p+(unsigned long long)k;
}
map<unsigned long long,int> fg;
long long work(int l,int r,int k){
	unsigned long long id=hsh(l,r,k);
	if(mp.count(id)) return mp[id];
	int maxd=maxn[l][r],mind=minn[l][r];
	long long ans1=a[maxd]-k;
	if(maxd>l) ans1+=work(l,maxd-1,k);
	if(maxd<r) ans1+=work(maxd+1,r,k);
	if(l!=r){
		long long sx=a[maxd]-a[mind]+1;
		long long mx=a[maxd]-k;
		long long val=(sx+mx)*(mx-sx+1)/2;
		long long ans2=val;
		if(mind>l) ans2+=work(l,mind-1,a[mind]);
		if(mind<r) ans2+=work(mind+1,r,a[mind]);
		ans1=min(ans1,ans2);
	}
	return mp[id]=ans1;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		minn[i][i]=maxn[i][i]=i;
		for(int j=i+1;j<=n;j++){
			minn[i][j]=minn[i][j-1];
			maxn[i][j]=maxn[i][j-1];
			if(a[minn[i][j-1]]>a[j]) minn[i][j]=j;
			if(a[maxn[i][j-1]]<a[j]) maxn[i][j]=j;
		}
	}
	cout<<work(1,n,0);
	return 0;
}

T4 [字符替换](https://www.gxyzoj.com/d/hzoj/p/4829

依旧不会,据说是猫树和另外一些神秘做法;

原题赛时锣鼓P8885,有时间再看看吧 (逃

posted @ 2025-08-10 19:33  zhangch_qwq  阅读(20)  评论(0)    收藏  举报