【比赛记录】2025CSP-S模拟赛4

A. 序列问题

首先有一个 DP,设 \(f_i\) 表示前 \(i\) 个位置,当前子序列长度为 \(a_i\),结尾也为 \(a_i\) 的最大价值。那么我们有:

\[f_i=\max_{j<i\land a_j<a_i\land i-j\ge a_i-a_j}\{f_j+1\} \]

考虑这三个限制条件,满足了后两个就一定满足第一个。第三个限制可以变形为 \(i-a_i\ge j-a_j\)。于是将原序列按 \(a\) 值排序,再 \(O(n\log n)\) 跑一个 \(i-a_i\) 最长不下降子序列即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define uprb upper_bound
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=5e5+5;
int n,f[maxn];
struct node{
	int a,p;
	il bool operator<(const node &x)const{
		return a<x.a||a==x.a&&p>x.p;
	}
}b[maxn];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>b[i].a;
		b[i].p=i;
	}
	sort(b+1,b+n+1);
	int cnt=0;
	for(int i=1;i<=n;i++){
//		cout<<b[i].p-b[i].a<<" ";
		if(b[i].a>b[i].p){
			continue;
		}
		if(!cnt||b[i].p-b[i].a>=f[cnt]){
			f[++cnt]=b[i].p-b[i].a;
		}
		else{
			*uprb(f+1,f+cnt+1,b[i].p-b[i].a)=b[i].p-b[i].a;
		}
	}
//	puts("");
	cout<<cnt;
	return 0;
}
}
int main(){return asbt::main();}

B. 钱仓

显然需要断环为链。

考虑一个链怎么处理,倒着扫,每次贪心地用当前的钱去填充最远的位置。断环为链后,显然我们有 \(n\) 个起点可供选择。那么在倒着扫的过程中记录一个后缀和,差分计算即可。

但是会有问题,就是当前这一段长为 \(n\) 的链中的钱有可能给到后面去了,也就是说这段链中不能还有 \(0\) 没被填充。判断一下更新答案即可。用队列维护 \(0\) 的位置,时间复杂度是线性的。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
const ll inf=1e18;
int n,a[maxn<<1],q[maxn<<1];
ll f[maxn<<1];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	freopen("barn.in","r",stdin);
	freopen("barn.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i+n]=a[i];
	}
	int hd=1,tl=0;
	ll ans=inf;
	for(int i=n<<1;i;i--){
		f[i]=f[i+1];
		while(a[i]&&hd<=tl){
			f[i]+=(q[hd]-i)*(q[hd]-i);
			a[i]--,hd++;
		}
		if(!a[i]){
			q[++tl]=i;
		} 
		if(i<=n&&(hd>tl||q[tl]>=i+n)){
			ans=min(ans,f[i]-f[i+n]);
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

Upd on 3.30:存在这么一个性质,当当前的长为 \(n\) 的链中没有未被填充的 \(0\),那么此时队列一定为空。

证明:因为当前这一段长为 \(n\) 的链的和一定为 \(n\),又是往后给的,那么给完后没有 \(0\) 和就一定依然为 \(n\)。换句话说,这一段中的钱都给了这一段了。而我们又是优先给最远的,因此这一段区间后面 一定也没有 \(0\)。也就是队列为空。

因此,我们更新答案就只用判断队列为空就好了。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
const ll inf=1e18;
int n,a[maxn<<1],q[maxn<<1];
ll f[maxn<<1];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	freopen("barn.in","r",stdin);
	freopen("barn.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i+n]=a[i];
	}
	int hd=1,tl=0;
	ll ans=inf;
	for(int i=n<<1;i;i--){
		f[i]=f[i+1];
		while(a[i]&&hd<=tl){
			f[i]+=(q[hd]-i)*(q[hd]-i);
			a[i]--,hd++;
		}
		if(!a[i]){
			q[++tl]=i;
		} 
		if(i<=n&&hd>tl){
			ans=min(ans,f[i]-f[i+n]);
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

C. 自然数

首先 \(O(n)\) 暴力算出 \(\operatorname{mex}(1,i)\) 的值。考虑从左往右扫 \(l\) 指针,用线段树维护 \(\sum_{r=l}^{n}\operatorname{mex}(l,r)\)

考虑 \(l\) 右移一位时,设 \(a_l\) 下一个出现的位置为 \(nxt_l\),则我们要将 \([l,nxt_l)\) 中所有大于 \(a_l\)\(\operatorname{mex}\) 值改为 \(a_l\)。注意到确定了 \(l\) 后,随着 \(r\) 的单增,\(\operatorname{mex}(l,r)\) 的值是单调不降的,于是我们去做线段树二分,再区间推平就行了。时间复杂度 \(O(n\log n)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
int n,a[maxn],b[maxn],bu[maxn];
int nxt[maxn],pos[maxn];
int sum[maxn<<2],tag[maxn<<2],mxz[maxn<<2];
il void pushup(int id){
	sum[id]=sum[lid]+sum[rid];
	mxz[id]=max(mxz[lid],mxz[rid]);
}
il void pushtag(int id,int l,int r,int x){
	tag[id]=mxz[id]=x;
	sum[id]=x*(r-l+1);
}
il void pushdown(int id,int l,int r){
	if(~tag[id]){
		int mid=(l+r)>>1;
		pushtag(lid,l,mid,tag[id]);
		pushtag(rid,mid+1,r,tag[id]);
		tag[id]=-1;
	}
}
il void build(int id,int l,int r){
	tag[id]=-1;
	if(l==r){
		sum[id]=mxz[id]=b[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void upd(int id,int L,int R,int l,int r,int x){
	if(L>=l&&R<=r){
		pushtag(id,L,R,x);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		upd(lid,L,mid,l,r,x);
	}
	if(r>mid){
		upd(rid,mid+1,R,l,r,x);
	}
	pushup(id);
}
il int qsum(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return sum[id];
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1,res=0;
	if(l<=mid){
		res+=qsum(lid,L,mid,l,r);
	}
	if(r>mid){
		res+=qsum(rid,mid+1,R,l,r);
	}
	return res;
}
il int qpos(int id,int L,int R,int l,int r,int x){
	if(mxz[id]<=x){
		return -1;
	}
	if(L==R){
		return L;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1,res=-1;
	if(l<=mid){
		res=qpos(lid,L,mid,l,r,x);
	}
	if(r>mid&&res==-1){
		res=qpos(rid,mid+1,R,l,r,x);
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	freopen("mex.in","r",stdin);
	freopen("mex.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1,res=0;i<=n;i++){
		if(a[i]<=n){
			bu[a[i]]++;
		}
		while(bu[res]){
			res++;
		}
		b[i]=res;
	}
	for(int i=n;i;i--){
		if(a[i]>n){
			continue;
		}
		nxt[i]=pos[a[i]];
		pos[a[i]]=i;
	}
	for(int i=1;i<=n;i++){
		if(!nxt[i]){
			nxt[i]=n+1;
		}
	}
	build(1,1,n);
	int ans=0;
	for(int i=1;i<=n;i++){
		ans+=qsum(1,1,n,i,n);
		int tmp=qpos(1,1,n,i,nxt[i]-1,a[i]);
		if(~tmp){
			upd(1,1,n,tmp,nxt[i]-1,a[i]);
		}
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}
/*
3
0 1 3
5
*/

D. 环路

将图存成邻接矩阵 \(bas\),那么我们的答案其实就是 \(bas+bas^2+bas^3+\dots+bas^{k-1}\) 的主对角线的和。而对于这个式子的计算是可以用一个分治非常轻松地解决的。具体地,如果当前项数为偶数,那么直接砍半;是奇数,就将末项去掉后砍半,然后再加上。时间复杂度 \(O(n^3\log^2k)\)。在计算过程中同时算末项,就是 \(O(n^3\log k)\) 了。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
int n,m,mod;
string s;
struct juz{
	int mat[105][105];
	juz(){
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				mat[i][j]=0;
			}
		}
	}
	il int*operator[](int x){
		return mat[x];
	}
	il juz operator*(juz x)const{
		juz res;
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				for(int k=0;k<n;k++){
					(res[i][j]+=mat[i][k]*1ll*x[k][j]%mod)%=mod;
				}
			}
		}
		return res;
	}
	il juz operator+(juz x)const{
		juz res;
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				res[i][j]=(mat[i][j]+x[i][j])%mod;
			}
		}
		return res;
	}
}bas;
il pair<juz,juz> solve(int k){
	if(k==1){
		return mp(bas,bas);
	}
	auto res=solve(k>>1);
	res.fir=res.fir+res.fir*res.sec;
	res.sec=res.sec*res.sec;
	if(k&1){
		res.sec=res.sec*bas;
		res.fir=res.fir+res.sec;
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	freopen("tour.in","r",stdin);
	freopen("tour.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>s;
		for(int j=0;j<n;j++){
			bas[i][j]=s[j]=='Y';
		}
	}
	cin>>m>>mod;
	juz res=solve(m-1).fir;
	int ans=0;
	for(int i=0;i<n;i++){
		(ans+=res[i][i])%=mod;
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}
posted @ 2025-03-29 14:05  zhangxy__hp  阅读(80)  评论(0)    收藏  举报