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

A. 序列问题
首先有一个 DP,设 \(f_i\) 表示前 \(i\) 个位置,当前子序列长度为 \(a_i\),结尾也为 \(a_i\) 的最大价值。那么我们有:
考虑这三个限制条件,满足了后两个就一定满足第一个。第三个限制可以变形为 \(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();}

浙公网安备 33010602011771号