2024年春招-美团-技术岗-第一批笔试

思路:直接预处理二维前缀和,然后对于每个符合情况的矩阵直接O(1)枚举求和即可

代码:

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=203;
char ss[N][N];
int s[N][N];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",ss[i]+1);
		for(int j=1;j<=n;j++)
		{
			if(ss[i][j]=='1') s[i][j]=1;
			s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		}
	}
	for(int len=1;len<=n;len++)
	{
		if(len%2==1)
		{
			printf("0\n");
			continue;
		}
		int ans=0;
		for(int i=len;i<=n;i++)
		for(int j=len;j<=n;j++)
			if(s[i][j]-s[i-len][j]-s[i][j-len]+s[i-len][j-len]==len*len/2)
				ans++;
		printf("%d\n",ans);
	}		

	
	return 0;
}

思路:最小情况对应把所有0都替换为l,最大情况对应把所有0都替换为r

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
long long a[N];
int main()
{
	int n,q;
	cin>>n>>q;
	int cnt=0;
	long long sum=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		sum+=a[i];
		if(a[i]==0) cnt++;
	}
	long long l,r;
	while(q--)
	{
		scanf("%lld%lld",&l,&r);
		printf("%lld %lld\n",sum+l*cnt,sum+r*cnt);
	}
	return 0;
}

思路:可以先把原来字符串中存在的M和T统计出来,然后如果其余字符小于等于k个那么直接输出n,否则再将其余字符中的k个换为MT即可

代码:

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
char s[N];
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	scanf("%s",s+1);
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		if(s[i]=='M'||s[i]=='T')
			ans++;
	}
	ans=min(n,ans+k);
	printf("%d",ans);
	return 0;
}

思路:这道题的核心是离线处理+逆向思维

并查集只能加边不能删边,但题目要求动态删边并查询连通性

因此需要先读入所有操作,标记哪些边会被删除

初始只加入永远不会被删除的边

从后向前处理操作:删除操作变成添加边,查询操作直接回答

可行性:逆序处理后,所有删边都变成了加边,符合并查集特性且第i次查询时的图状态,等于逆序处理到第i次操作时的图状态

最后需要注意离散化

代码1:

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int fu[N],u[N],v[N];
int op[N],op_u[N],op_v[N],ans[N];
map<pair<int,int>, int>mp;
vector<int> alls;//用于离散化 
int find_alls(int x)
{
	return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
int find(int x)
{
	if(x!=fu[x]) return fu[x]=find(fu[x]);
	return x;
}
int main()
{
	int n,m,q;
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&u[i],&v[i]);
		alls.push_back(u[i]);
		alls.push_back(v[i]);
	}
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d",&op[i],&op_u[i],&op_v[i]);
		alls.push_back(op_u[i]);
		alls.push_back(op_v[i]);
	}
	sort(alls.begin(),alls.end());
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	int len=alls.size();
	for(int i=1;i<=len;i++)
		fu[i]=i; 
	// 离散化初始关系
	for(int i=1;i<=m;i++)
	{
		u[i]=find_alls(u[i]);
		v[i]=find_alls(v[i]);
		mp[{u[i],v[i]}]=mp[{v[i],u[i]}]=1;// 标记初始存在的关系
	}
	// 处理事件,标记哪些关系会被删除
	for(int i=1;i<=q;i++)
	{
		op_u[i]=find_alls(op_u[i]);
		op_v[i]=find_alls(op_v[i]);
		if(op[i]==1&&mp[{op_u[i],op_v[i]}]==1)
			mp[{op_u[i],op_v[i]}]=mp[{op_v[i],op_u[i]}]=2;//标记这段关系之前存在过 
	}
	// 建立初始图:只加入那些永远不会被删除的边
	for(int i=1;i<=m;i++)
	{
		if(mp[{u[i],v[i]}]==1)//证明关系存在且没被淡忘 
		{
			int f_u=find(u[i]),f_v=find(v[i]);
			if(f_u!=f_v) fu[f_u]=f_v;
		}
	}
	// 逆序处理事件
	for(int i=q;i>=1;i--)
	{
		int f_u=find(op_u[i]),f_v=find(op_v[i]);
		if(op[i]==1&&mp[{op_u[i],op_v[i]}]==2)//他们之前必须存在朋友关系才能淡忘,否则无效 
		{
			if(f_u!=f_v) fu[f_u]=f_v;
		}
		else if(op[i]==2)
		{
			if(f_u==f_v) ans[i]=1;//两者在同一个集合中 
		}
	}
	for(int i=1;i<=q;i++)
	{
		if(op[i]==2)
		{
			if(ans[i]==1) printf("Yes\n");
			else printf("No\n");
		}
	}
	return 0; 
}

代码2:

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int fu[N],u[N],v[N];
int op[N],op_u[N],op_v[N],ans[N];
map<pair<int,int>, int>mp;
vector<int> alls;//用于离散化 
int find_alls(int x)
{
	return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
int find(int x)
{
	if(x!=fu[x]) return fu[x]=find(fu[x]);
	return x;
}
int main()
{
	int n,m,q;
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&u[i],&v[i]);
		alls.push_back(u[i]);
		alls.push_back(v[i]);
	}
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d",&op[i],&op_u[i],&op_v[i]);
		alls.push_back(op_u[i]);
		alls.push_back(op_v[i]);
	}
	sort(alls.begin(),alls.end());
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	int len=alls.size();
	for(int i=1;i<=len;i++)
		fu[i]=i; 
	// 离散化初始关系
	for(int i=1;i<=m;i++)
	{
		u[i]=find_alls(u[i]);
		v[i]=find_alls(v[i]);
		mp[{u[i],v[i]}]=mp[{v[i],u[i]}]=1;// 标记初始存在的关系
	}
	// 处理事件,标记哪些关系会被删除
	for(int i=1;i<=q;i++)
	{
		op_u[i]=find_alls(op_u[i]);
		op_v[i]=find_alls(op_v[i]);
		if(op[i]==1&&mp[{op_u[i],op_v[i]}]>=1)
		{
			mp[{op_u[i],op_v[i]}]++;//标记这段关系之前存在过 
			mp[{op_v[i],op_u[i]}]++;
		}
	}
	// 建立初始图:只加入那些永远不会被删除的边
	for(int i=1;i<=m;i++)
	{
		if(mp[{u[i],v[i]}]==1)//证明关系存在且没被淡忘 
		{
			int f_u=find(u[i]),f_v=find(v[i]);
			if(f_u!=f_v) fu[f_u]=f_v;
		}
	}
	// 逆序处理事件
	for(int i=q;i>=1;i--)
	{
		int f_u=find(op_u[i]),f_v=find(op_v[i]);
		if(op[i]==1&&mp[{op_u[i],op_v[i]}]>=2)//他们之前必须存在朋友关系才能淡忘,否则无效 
		{
			if(mp[{op_u[i],op_v[i]}]==2)//恰好是这次删除操作导致断裂 
			{
				if(f_u!=f_v) fu[f_u]=f_v;
			}
			mp[{op_u[i],op_v[i]}]--;
			mp[{op_v[i],op_u[i]}]--; 
		}
		else if(op[i]==2)
		{
			if(f_u==f_v) ans[i]=1;//两者在同一个集合中 
		}
	}
	for(int i=1;i<=q;i++)
	{
		if(op[i]==2)
		{
			if(ans[i]==1) printf("Yes\n");
			else printf("No\n");
		}
	}
	return 0; 
}

上述两个代码区别:

两个代码都能AC,但第一个代码弱一些,题目中并未涉及到对一段关系多次淡化的操作,比如:

操作1: 1 1 2 (第一次淡忘)

操作2: 2 1 2 (查询)

操作3: 1 1 2 (第二次淡忘同一个关系)

在代码1中如果倒序的加边会发现,在操作3后会恢复1和2的关系,那么查询1和2就是Yes,但实际上已经在操作1中淡忘了,所以应该输出No,这是代码数据中没有涉及的问题,因此为了处理这个问题,我们应该在第一次淡忘的时候才选择加边,因此我在代码2中利用int型变量来记录淡忘次数,当等于2(代表第一次淡忘时才选择恢复关系)

思路:乘积末尾至少有k个0意味着乘积中的质因子2和质因子5都至少含有k个

那么可以直接预处理前缀和后缀2的质因子数以及前缀5和后缀5的质因子数

接着枚举区间左端点,由于是乘积末尾至少含有k个0,因此我们删除区间越短会导致乘积含有更多的0 ,因此区间长度具备二分性,因此可以二分解决

代码:

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int pre_2[N],pre_5[N],suf_2[N],suf_5[N];
int main()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		pre_2[i]=pre_2[i-1];
		pre_5[i]=pre_5[i-1];
		int t=a[i];
		while(t%2==0)
		{
			t/=2;
			pre_2[i]++;
		}
		while(t%5==0)
		{
			t/=5;
			pre_5[i]++;
		}
	}
	for(int i=n;i>=1;i--)
	{
		suf_2[i]=suf_2[i+1];
		suf_5[i]=suf_5[i+1];
		int t=a[i];
		while(t%2==0)
		{
			t/=2;
			suf_2[i]++;
		}
		while(t%5==0)
		{
			t/=5;
			suf_5[i]++;
		}
	}
	long long ans=0; 
	for(int i=1;i<=n;i++)//枚举删除区间的左端点 
	{
		int need_2=k-pre_2[i-1],need_5=k-pre_5[i-1];//记录后缀需要的2和5的个数 
		int l=i-1,r=n;
		while(l<r)
		{
			int mid=l+r+1>>1;
			if(suf_2[mid+1]>=need_2&&suf_5[mid+1]>=need_5) l=mid;
			else r=mid-1;
		}
		// [i,l]任意选一个作为删除区间右端点即可 
		ans+=l-i+1;
	}
	printf("%lld",ans); 
	return 0;

写在最后:时间过得真快,上一次记录题解时还是在大学,转眼间已经要到暑期实习找工作了,之后会不定期更新一些大厂笔试题解,希望能和大家多多交流。