5.1 模拟赛总结
队伍: zpl & lsc
比赛过程大概就是 8:00 开题,发现 A 跟哈希有关,就大胆开 A ,最开始想的是用一个桶来存哈希值。
对这个桶建线段树,然后实现单点修改区间加,发现区间不连续,一整个无语住了。这时候已经过去了将近一个小时。
然后就开始干暴力,写了一会 lsc 问我可不可以分块,我想了想觉得分块不大行,但根号分治好像可以。
于是又口胡了一会,胡除了正解,估摸了一下码量,发现很小,可以写。
大概就是我们取 \(\sqrt{n}\) 为分界线,如果 \(p\le \sqrt{n}\) 则我们记录以 \(p\) 为模的池,提前预处理出来,查询时直接输出即可。否则的话我们就直接暴力跑就好了。
#include<bits/stdc++.h>
using namespace std;
const int N=510;
const int M=150010;
int ans[N][N];
int a[M];
int n,m,cur;
int sz;
int main()
{
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
cin>>n>>m;
sz=sqrt(n);
for(int i=1;i<=n;i++)
{
cin>>cur;
a[i]=cur;
for(int p=1;p<=sz;p++)
{
ans[p][i%p]+=cur;
}
}
while(m--)
{
int x,y;
char opt;
cin>>opt>>x>>y;
if(opt=='A')
{
if(x<=sz)
{
cout<<ans[x][y]<<endl;
}
else
{
int sum=0;
for(int i=y;i<=n;i+=x) sum+=a[i];
cout<<sum<<endl;
}
}
else
{
for(int p=1;p<=sz;p++)
{
ans[p][x%p]-=(a[x]-y);
}
a[x]=y;
}
}
return 0;
}
交了一发,过了,然后 lsc 说他会 B 了,我就没看,去写 C 。
看题发现有个询问年龄大于等于 \(B\) 且第 \(Y\) 站以前下车的最年轻的小孩多大,也就是说意思是求在 \([b,n]\) 这个区间内小于 \(Y\) 的最小位置,第一反应这他妈不是二分答案裸题吗。
但问题在于他不单调,无语住了。考虑到这是数据结构专场,于是考虑用数据结构维护。
看了眼数据范围,发现必须要 \(\log\) 的复杂度,再考虑到已经有二分了,果断线段树。
可以以年龄 \([1,n]\) 为下标来建线段树,我们维护其区间最小值(也就是年龄为 \(i\) 的人下车的位置),然后添加操作就相当于单点修改,查询就是区间查询。
维护的话很简单,对于一个区间,我们先看最小值是否比我们要的询问值小,如果是的话,就说明这个答案在这个区间内,然后就递归地找这个区间最小的叶子节点,找到就做完了。
感觉码量会很大,于是又思考了一下实现。
不难发现,我实现只需要一个 find 一个 update 和一个 query 。
update 维护最小站点的位置。
find 就是 \([l,r]\) 覆盖的区间就在这区间里找满足要求的最小年龄。
query 查询。
然后发现或许可以把 update 和 find 放在一起写。
看样子不难写,于是给 lsc 看了一眼具体写法我就去写了,此时 lsc 还在写 B 。
然后就开始痛苦地 1.5h 写,调试,交。
刚开始翻车了,结果发现我 update 没返回,脑子抽了,然后调了一会就过了。
#include<bits/stdc++.h>
using namespace std;
const int M=200010;
#define ls x<<1|1
#define rs x<<1
int tree[M<<2];
int n,q,v,k,u;
int update(int l,int r,int x)
{
if(l==r)
{
tree[x]=u;
return tree[x];
}
int mid=(l+r)>>1;
int ans;
if(v>mid)
{
ans=update(mid+1,r,ls);
}
else
{
ans=update(l,mid,rs);
}
tree[x]=min(tree[x],ans);
return tree[x];
}
int query(int l,int r,int x)
{
if(tree[x]>u) return 400000;
if(l==r) return l;
int mid=(l+r)>>1;
int ans;
if(mid<v)
{
return query(mid+1,r,ls);
}
ans=query(l,mid,rs);
if(ans<200000)
{
return ans;
}
else
{
return query(mid+1,r,ls);
}
}
int main()
{
// freopen("c.in","r",stdin);
// freopen("c.out","w",stdout);
cin>>n>>q;
memset(tree,127,sizeof(tree));
while(q--)
{
char opt;
cin>>opt;
cin>>u>>v;
if(opt=='M') update(1,n,1);
else
{
int ans=query(1,n,1);
if(ans>200000) cout<<"-1\n";
else cout<<ans<<"\n";
}
}
return 0;
}
接下来我开始思考 D 。
发现 D 50 pts 只需要维护一个二维前缀和,于是果断开写。
维护两个前缀和。
\(sum_{i,j,k}\)表示从 \((1,1)\) 到 \((i,j)\) 的矩阵中数值 \(\ge k\) 的数的总和。
\(cnt_{i,j,k}\)表示从 \((1,1)\) 到 \((i,j)\) 的矩阵中数值 \(\ge k\) 的数的个数。
然后就二分答案求最小值就好了。
但由于前面的时间浪费的太多了,由于比较害怕写不完了,没细想就开始写了,然后就写了 50min 都没写出来,后来调出来了,但比赛已经结束了。
所以结局就是 lsc 贡献了 0pts,我贡献了 200pts 。
对面那个队伍是 why 贡献了 44pts,hcw 201pts,大无语。
不过还好,起码我敢写了(
这是什么年代连平时只口胡的 zpl 都开始写代码了(
这场模拟赛让我明白了思维的重要性,这场题只要想到用什么数据结构就结束了,而且都是学过的。
所以说时间复杂度分析至关重要啊。
不要不敢写,最怕不敢写,但也要想清楚再写。

浙公网安备 33010602011771号