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 查询。

然后发现或许可以把 updatefind 放在一起写。

看样子不难写,于是给 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 都开始写代码了(

这场模拟赛让我明白了思维的重要性,这场题只要想到用什么数据结构就结束了,而且都是学过的。

所以说时间复杂度分析至关重要啊。

不要不敢写,最怕不敢写,但也要想清楚再写。

posted @ 2022-05-02 13:05  zplqwq  阅读(64)  评论(0)    收藏  举报