2025.8.9 CSP-S模拟赛31
小长假回来后的第一场模拟赛
159pts,没啥技术含量,就是暴力打满(挂了10pts
T1 花海
赛时只打了50分暴力;
这种类型的题目以前基本没遇见过,但是思路很重要;
观察到数据范围,直接暴力矩阵甚至会爆空间,观察到 \(n*m \leq 2*10^5\) ,拿吗我们假设其中 \(n<m\) ,那么有 \(n \leq \sqrt{2*10^5}\) ;时限有3s,于是我们可以考虑到 \(O(n^2m)\) 做法;
借用cql大佬的思路,我们枚举两行作为矩阵上下界,然后自左向右枚举矩阵的左边界并将其记为 \(mxl_i\),同时统计以 \(i\) 为起始向右拓展的最大矩阵边界值;因为前缀和优化可以 \(O(1)\) 求出矩阵值,所以上述操作可以 \(O(m)\) 完成;
可以看下图,基本就是这样统计,重叠部分抵消:

还有不懂得可以看代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10,MAXM=5e2+10;
int n,m,a[MAXM][MAXN],qzh[MAXM][MAXN];
int maxn;
vector<int> ls;
int mxl[MAXN],mxr[MAXN];
int get_(int xf,int yf,int xs,int ys){
return qzh[xs][ys]-qzh[xf-1][ys]-qzh[xs][yf-1]+qzh[xf-1][yf-1];
}
int main(){
freopen("flower.in","r",stdin);
freopen("flower.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m;
if(n<m){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
}
else{
for(int i=1;i<=n*m;i++){
int x;cin>>x;
ls.push_back(x);
}
swap(n,m);
for(int i=1;i<=n;i++){
for(int p=1,j=0;p<=m;p++,j+=n){
a[i][p]=ls[i+j-1];
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
qzh[i][j]=a[i][j]+qzh[i-1][j]+qzh[i][j-1]-qzh[i-1][j-1];
}
}
for(int l=1;l<n;l++){
for(int r=l+1;r<=n;r++){
int sum=0;
for(int i=1;i<=m;i++) mxl[i]=get_(l+1,i,r-1,i)-sum,sum+=a[l][i]+a[r][i];
sum=0;
for(int i=m;i>=1;i--) mxr[i]=get_(l+1,i,r-1,i)-sum,sum+=a[l][i]+a[r][i];
for(int i=m-1;i;i--) mxr[i]=max(mxr[i],mxr[i+1]);
for(int i=1;i<m;i++) maxn=max(maxn,sum+mxl[i]+mxr[i+1]);
}
}
cout<<maxn;
return 0;
}
T2 划分
令\(f_i\)表示\(1-i\)的划分的最大值和最小值
然后考虑后面部分可以用一个单调队列维护,然后就完了;
代码如下:
#include<bits/stdc++.h>
using namespace std;
long long n,m,a[100005],f[100005][20],qz[100005];
long long minn[100005];
long long lx=0,rx,up_line,ans=1e18,maxn;
long long dp[100005];
unsigned long long pf=13331,ps=131;
long long que[100005],head=1,teil,xb=0;
int rmq_query(int l,int r){
int k=log2(r-l+1);
return max(f[l][k],f[r-(1<<k)+1][k]);
}
int main(){
freopen("split.in","r",stdin);
freopen("split.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m;
minn[0]=1e18;
for(int i=1;i<=n;++i){
cin>>a[i];
qz[i]=qz[i-1]+a[i];
f[i][0]=a[i];
}
for(int j=1;(1<<j)<=n;++j){
for(int i=1;i+(1<<j)-1<=n;++i){
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
memset(dp,0x3f3f3f3f,sizeof dp);
dp[0]=0;
for(int i=1;i<=n;i++){
while(qz[i]-qz[xb]>m) xb++;
while(head<=teil&&que[head]<=xb) head++;
while(head<=teil&&a[que[teil]]<a[i]) teil--;
que[++teil]=i;
dp[i]=min(dp[i],dp[xb]+a[que[head]]);
for(int j=head;j<teil;j++){
dp[i]=min(dp[i],dp[que[j]]+a[que[j+1]]);
}
}
cout<<dp[n];
return 0;
}
T3 落子无悔
使用zhangxy大佬的思路,题解直接看就行

当然主播不会用set,所以用优先队列打标记完成,代码如下:
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,fa[maxn],p[maxn],a[maxn];
int _0[maxn],_1[maxn],nxt[maxn],lst[maxn];
il int find(int x){
return x!=p[x]?p[x]=find(p[x]):x;
}
struct node{
int u,_0,_1;
node(int u=0,int _0=0,int _1=0):u(u),_0(_0),_1(_1){}
il bool operator<(const node &x)const{
if(_0*x._1==x._0*_1){
return u<x.u;
}
return _0*x._1>x._0*_1;
}
};
set<node> S;
int main(){
freopen("board.in","r",stdin);
freopen("board.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=2;i<=n;i++){
cin>>fa[i];
}
for(int i=1;i<=n;i++){
cin>>a[i];
_0[i]=a[i]^1,_1[i]=a[i];
p[i]=i,nxt[i]=lst[i]=i;
}
for(int i=2;i<=n;i++){
S.insert(node(i,_0[i],_1[i]));
}
for(int i=1;i<n;i++){
int u=S.begin()->u,v=find(fa[u]);
S.erase(node(u,_0[u],_1[u]));
if(v!=1){
S.erase(node(v,_0[v],_1[v]));
}
_0[v]+=_0[u],_1[v]+=_1[u];
nxt[lst[v]]=u,lst[v]=lst[u];
p[u]=v;
if(v!=1){
S.insert(node(v,_0[v],_1[v]));
}
}
ll cnt=0,ans=0;
for(int i=1;;i=nxt[i]){
if(!a[i]){
ans+=cnt;
}
else{
cnt++;
}
if(i==nxt[i]){
break;
}
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
T4 体测
30%
对于 $n \leq 6 $ ,\(l,r \leq 10\) 的情况,直接爆搜即可;
50%
对于 \(a_i=1\) 的情况,直接因为单个项目一天只能满足一个,直接判断多个约束条件的总上界减总下界是否足够 \(n\) ,否则就无解;
100%
什么?真的吗?你听说了吗?我真的会吗?
直接粘题解吧:
如果每个右端点不一样,可以通过每次取右端点构造一个方案
反之
我们来构造一个右端点不一样的区间
对右端点从大到小排序,得到{r1,r2,……},对于相同的r,可以将他们变成{r,r-1,r-2……},这便得到了最优的右端点序列
一个很显然的想法,让左端点较小的区间有较小的右端点显然更优
然后开始分配右端点,按左端点从大到小排序后,每次取离r最近且<=r的右端点,顺便判Sorry
这可以用set实现;
显然一定存在一个答案最小的区间每次选择的点都是右端点(调整法)
之后按右端点从小到大选择必须的
每次找出左端点小于等于当前右端点的全部区间,按不同的种类删去右端点最小的区间,可以用set维护
时间复杂度为 \(O(nlgn)\)
本文来自博客园,作者:zhangch_qwq,转载请注明原文链接:https://www.cnblogs.com/zhangchenhua-awa/p/19035501

浙公网安备 33010602011771号