7-18小测

本人小测分:100+100+40+0( ̄ー ̄)

由于本人比较菜,所以本人将自己写的题解和我老师写的题解放在一起帮助各位学习

第1题 区间求和(sum)

panda在学习区间求和的时候遇到了困难,想向你求助。
但今天panda很忙,所以他也不想浪费时间,于是直接把题目给了你:
给定一个长度为 n 的正整数序列 a_1,a_2,...,a_n,和一个整数 m,求有多少个区间 [l,r],使区间 [l,r]内 a_i和为 m 。

输入格式

第一行包含两个整数 n,m 。
第二行包含 n 个正整数,分别为 a_1,a_2,...,a_n 。

输出格式

输出一行一个整数,表示和为 m 的区间个数。

思路:

image

非常简单的尺取模版

代码:

由于老师的思路和我的不同所以把老师的代码放过来了
image

#include<bits/stdc++.h>
using namespace std;
int n,a[100005];
long long m;
int main(){
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	int l=1,r=1;
	long long y=a[1],t=0;
	queue<int>q;
	q.push(a[1]);
	while(r<=n)
	{
		if(y<=m)
		{
			if(y==m)t++;
			q.push(a[++r]);
			y=y+a[r];
		}
		else
		{
			y=y-a[l];
			l++;
			q.pop();
		}
	}
	printf("%lld",t);
	
	return 0;
} 

第2题 社交网络服务(sns)

在一个社交网络服务(SNS)中,有N个用户,分别用从1到N的数字标记。
在这个SNS中,两个用户可以成为朋友。友谊是双向的;如果用户X是用户Y的朋友,那么用户Y总是用户X的朋友。
目前,在SNS上有M对朋友关系,第i对由用户A_i和B_i组成。
确定以下操作可以执行的最大次数:
操作:选择三个用户 X 、Y和 Z ,使得 X 和 Y 是朋友,Y 和 Z 是朋友,但 X 和 Z不是朋友。让 X 和 Z 成为朋友。

输入格式

第一行包含两个正整数 N 和 M,表示有 N 个用户,M 对朋友关系
接下来 M 行,每行描述第 i 对朋友关系。

输出格式

一行一个整数,表示答案。

思路:

image

我们观察题目,可以发现最后的网络用户朋友关系图一定是一个个完全图,也就是说当a和b可以通过路径到达时他们最后一定要相互交朋友的
所以我们可以用并查集来记录点的集合,并记录各个集合的点数和边数以便算还要连多少条边可以变成完全图

代码:

#include<bits/stdc++.h>
using namespace std;
long long n,m,father[200005],d[200005],b[200005],y=0;
bool jihe[200005];
int find(int x)
{
	if(father[x]==x)return x;
	return father[x]=find(father[x]);
}
int main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)
	{
		father[i]=i;
		d[i]=1;
		b[i]=0;
	}
	for(int i=1;i<=m;i++)
	{
		long long u,v;
		scanf("%lld%lld",&u,&v);
		int uu=find(u),vv=find(v);
		if(uu!=vv)
		{
			father[uu]=vv;
			d[vv]+=d[uu];
			jihe[uu]=0;
			b[vv]=b[vv]+b[uu]+1;
		}
        else b[vv]++;
	}
	for(int i=1;i<=n;i++)
	{
		if(find(i)==i)
		{
			y=y+d[i]*(d[i]-1)/2-b[i];
		}
	}
	printf("%lld",y);
	
	return 0;
}

第3题 乱来(seq)

kkkw给出了长度为 n 的序列 a 和一个正整数 k。
kkkw可以进行k次操作,每次操作,kkkw需要选择一个下标i∈[1,n],让 a_i变成 2a_i。
请你告诉kkkw,k 次操作以后 a_1+ a_2+...+ a_n的值最小可以是多少,由于答案可能很大,你需要输出答案对 10^9+7取模。

输入格式

第一行,两个正整数 n,k。
第二行,给定几个正整数 a_1,a_2,...,a_n。

输出格式

答案

思路:

题目思路分析

  1. 操作本质
    每次操作只能让一个数变成原来的两倍,每次操作都会让最终的总和变大。题目要求必须进行恰好 k 次操作。我们的目标,是让这些操作带来的总和增量最小。因此,每次都应该选择当前最小的数进行翻倍。
  2. 贪心策略
    为了让总和最小化,应该优先把操作分配给当前值最小的元素。直观理解:同样是翻倍,翻倍小的数比翻倍大的数对总和的增长影响更小。所以,只要每次都选择当前最小的数去翻倍,总和就会最小。
  3. 操作次数如何分配
    因为操作次数 k 可能远大于数字个数 n,所以每个数被翻倍的次数不会相差超过 1。
    具体分配方法如下:
    先把操作平均分配给所有的数,每个数都操作“k 除以 n”的整数部分次。
    剩下的“k 除以 n 的余数”次操作,分配给原始数值最小的那几个数。也就是说,最小的几个数多翻倍一次。
    这样分配后,总和能够达到最小。
  4. 排序后分配操作
    先把所有数从小到大排序。排序之后,前面“余数”个最小的数被操作的次数比其他的多一次,剩下的数都被操作“平均值”次。
  5. 如何计算最终的和
    每个数在被翻倍若干次后,变成原始数乘以 2 的若干次方。最后,把所有数的结果加起来,就是最终的答案。
    如果答案很大,按照题目要求,对一十亿零七取余。
  6. 算法效率分析
    整个过程只需对数组排序一次,复杂度为 n log n,剩下的分配和求和都是线性的,能够满足大数据范围。
  7. 总结
    本题的关键是:把操作尽量平均分配给所有数,并优先让原始值最小的那些数多分配操作。这样,最后的总和一定是最小的。

这道题我们可以运用贪心的思想,如果还不够就用快速幂找一遍2的k/n次方即可

代码:

#include<bits/stdc++.h>
#pragma GCC optinize(3)
using namespace std;
int n,k;
priority_queue<long long,vector<long long>,greater<long long>>q;
long long a[100005],size=0;
long long ksm(long long a,long long b)
{
	long long r=1;
	a=a%1000000007;
	while(b)
	{
		if(b&1)
		{
			r=(r*a)%1000000007;
		}
		a=(a*a)%1000000007;
		b/=2;
	}
	return r;
}
int main(){
    scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		long long x;
        scanf("%lld",&x);
		q.push(x);
	}
	while(k&&q.top()<(1<<29))
	{
		k--;
		long long f=q.top();
		q.pop();
		f=f*2;
		q.push(f);
	}
	while(q.size())
	{
		a[++size]=q.top();
		q.pop();
	}
	long long y=0,s=ksm(2,k/n);
	for(int i=1;i<=n;i++)
	{
		if(i<=k%n)
		{
			y=(y+a[i]*2%1000000007)%1000000007;
		}
		else
		{
			y=(y+a[i])%1000000007;
		}
	}
    printf("%lld",y*s%1000000007);
	
    return 0;
}

第4题 游览计划(plan)

洛谷题目传送门
panda玩猫和老鼠手游玩累了,于是kkkw带panda来到了一个旅游景点散散心。
这个旅游景点的地图共有 n 处地点,在这些地点之间连有 m 条双向道路。旅客从一个景点前往另一个景点需要乘坐景点提供的旅游大巴,旅游大巴会按照经过道路条数最少的路线行驶。
kkkw购买的景区套票让kkkw只能游览这 n 处地点中的 4 处,而且kkkw喜欢浏览沿途的风景,所以kkkw希望选出这 4 处不同的景点 a,b,c,d,使 a->b,b->c,c-> d 这三条旅游大巴行驶路线经过的道路数量总和最多。
你只需要输出最多的道路数量是多少。

输入格式

第一行两个整数 n,m 。
接下来 m 行,每行两个整数 x_i,y_i,表示第 i 条道路连接了第 x_i 和 y_i 处地点。

输出格式

共一行一个整数,表示答案。

思路:

image
如果用暴力枚举四个点明显会爆,于是我们可以只枚举两个点,剩下两个点就是枚举两个点分别的最远(注意一定是不同的点)。
处理枚举两个点时可以存下枚举两个点第一远,第二远,第三远来避免i的最远等于j或j最远的情况

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,d[4005][4005];
vector<int>a[4005];
pair<int,int>ma[4005][3];
bool v[4005];
void bfs(int x)
{
	for(int i=1;i<=n;i++)
	{
		d[x][i]=-1;
	}
	d[x][x]=0;
	queue<int>q;
	q.push(x);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=0;i<a[u].size();i++)
		{
			int v=a[u][i];
			if(d[x][v]==-1)
			{
				d[x][v]=d[x][u]+1;
				q.push(v);
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	for(int i=1;i<=n;i++)
	{
		bfs(i);//最短路计算
		ma[i][0]={0,0};//第一远
		ma[i][1]={0,0};//第二远
		ma[i][2]={0,0};//第三远
		for(int j=1;j<=n;j++)
		{
			if(d[i][j]>ma[i][0].first)
			{
				ma[i][2]=ma[i][1];
				ma[i][1]=ma[i][0];
				ma[i][0]={d[i][j],j};
			}
			else if(d[i][j]>ma[i][1].first)
			{
				ma[i][2]=ma[i][1];
				ma[i][1]={d[i][j],j};
			}
			else if(d[i][j]>ma[i][2].first)
			{
				ma[i][2]={d[i][j],j};
			}
		}
	}
	int y=0;
	for(int i=1;i<n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			v[i]=v[j]=1;
			//i最远的->i->j->j最远的
			for(int l=0;l<3;l++)
			{
				if(v[ma[i][l].second]==0)
				{
					v[ma[i][l].second]=1;
					for(int k=0;k<3;k++)
					{
						if(v[ma[j][k].second]==0)
						{
							y=max(ma[j][k].first+ma[i][l].first+d[i][j],y);
							break;
						}
					}
					v[ma[i][l].second]=0;
					break;
				}
			}
			//j最远的->j->i->i最远的
			for(int l=0;l<3;l++)
			{
				if(v[ma[j][l].second]==0)
				{
					v[ma[j][l].second]=1;
					for(int k=0;k<3;k++)
					{
						if(v[ma[i][k].second]==0)
						{
							y=max(ma[i][k].first+ma[j][l].first+d[i][j],y);
							break;
						}
					}
					v[ma[j][l].second]=0;
					break;
				}
			}
			v[i]=v[j]=0;
		}
	}
	printf("%d",y);
	
	return 0;
}

欢迎大家在评论区开喷

posted @ 2025-07-23 21:35  PLJZ  阅读(26)  评论(0)    收藏  举报