0715练习赛

A 烧饼管够

问题描述

何老板来到一家自助餐馆,这里的烧饼可以随便吃。
烧饼叠成A,B两摞,烧饼的大小不同,吃掉它所花时间也有不同。
A摞有个烧饼,从上到下吃掉第i个需耗时ai分钟;
B摞有个烧饼,从上到下吃掉第j个需耗时bj分钟;
两摞可以任意取,不过只能按从上往下的顺序依次取饼来吃。
何老板还有k分钟的时间,他想知道,他最多能吃掉多少个烧饼?

1N,M200000

1K109

1ai,bi109

输入格式

第一行,一个整数n
第二行, a1,a2,...an
第三行,一个整数m
接下来m行,每行两个整数p,k,表示一道题目 。

输出格式

m行,每行一个整数,表示一道题目的答案

样例输入1

3 4 240
60 90 120
80 150 80 150

样例输出1

3

样例输入2

3 4 730
60 90 120
80 150 80 150

样例输出2

7

一眼看过去好像有点像贪心,但仔细一想,如果按照每摞最小的取,则这组数据会有问题

6 6 105
100 1 1 1 1 1
99 99 99 99 99 99

但发现每次只能从上往下依次取饼来吃
所以每摞上取的饼一定是该段序列的前缀
考虑维护ai,bj的前缀和,
暴力枚举sumai,在sumb上二分查询或twopointer找其最大的符合条件的(<=ksumai)
耗时O(nlogn)/O(n)

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 200000 + 5;
int n,m,ans;
ll a[MAX_N],b[MAX_N],k,sa[MAX_N],sb[MAX_N];
int main(){
	scanf("%d%d%lld",&n,&m,&k);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		sa[i]=sa[i-1]+a[i];
	}
	for(int i=1;i<=m;i++){
		scanf("%lld",&b[i]);
		sb[i]=sb[i-1]+b[i];
	}
	int p=m;
	for(int i=0;i<=n;i++){
		if(sa[i]>k) break;
		while(sb[p]>k-sa[i] && p>=0) p--; 
		ans=max(ans,i+p);
	}
	printf("%d",ans);
	return 0;
}

B 加法练习

问题描述

何老板教小朋友做加法。
何老板给出一个正整数数列a1,a2,...,an ,其中任意元素ai都满足1<=ai<=n
何老板给小朋友们布置了m道加法题:
每道题给出两个整数p,k,小朋友可以重复执行加法操作,每一次操作,将p的值改为p+ap+k,问最少几次操作,就能使p>n
每道题目都需要小朋友快速给出答案。

1n,m105

1p,kn

1ain

输入格式

第一行,一个整数n
第二行, a1,a2,...,an
第三行,一个整数m
接下来行,每行两个整数p,k,表示一道题目。

输出格式

m行,每行一个整数,表示一道题目的答案

样例输入 1

3
1 1 1
3
1 1
2 1
3 1

样例输出 1

2
1
1

样例输入 2

10
3 5 4 3 7 10 6 7 2 3
10
4 5
2 10
4 6
9 9
9 2
5 1
6 4
1 1
5 6
6 4

样例输出2

1
1
1
1
1
1
1
2
1
1

考虑根号分治,对k进行分治

对于k<=n,进行预处理
f[i][j]表示p=i,k=j使p>n的操作数
f[i][j]={f[i+ai+j][j]+1(ai+i+jn)1(ai+i+j>n)

对于k>nk很大,每次操作加的就很多,直接暴力模拟,最多加n次,所以O(nn)

code:耗时O(nn)

#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 100000 + 5;
const int MAX_S = 350 + 5;
int n,m,a[MAX_N],p,k,f[MAX_N][MAX_S];
int main(){
//	freopen("badd.in","r",stdin);
//	freopen("badd.out","w",stdout);
	scanf("%d",&n);
	int sqrtn=sqrt(n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=n;i>=1;i--){
		for(int j=sqrtn;j>=1;j--){
			if(i+a[i]+j>n) f[i][j]=1;
			else f[i][j]=f[i+a[i]+j][j]+1;
		}
	}
	scanf("%d",&m);
	while(m--){
		scanf("%d%d",&p,&k);
		int ans=0;
		if(k<=sqrtn){
			printf("%d\n",f[p][k]);
		}
		else{
			while(p<=n){
				ans++;
				p=p+a[p]+k;
			}
			printf("%d\n",ans);
		}
	}
	return 0;	
}

C 公约数和

问题描述

何老板用[1,K]内的整数构成一个长度为N的整数数列A1,A2,...,AN
他发现可以构造出KN种不同的数列。
对于其中第种数列,其公约数Gi=gcd(A1,A2,...,AN)
何老板想请你帮忙计算出每个数列的公约数,并求出这些公约数之和。也就是计算G1+G2+...+GKN
答案可能很大 mod (109+7)再输出

2N105

1K105

输入格式

第一行,两个整数N,K

输出格式

一个整数,表示计算结果

样例输入 1

3 2

样例输出 1

9

样例输入 2

3 200

样例输出 2

10813692

样例输入 3

100000 100000

样例输出 3

742202979

因为有KN种不同数列,算出每个数列公约数不现实,考虑算出每个数列对答案的贡献。

又因为1K105,我们考虑[1,K]内每个数字对答案的贡献。对于数字x,在[1,K]内,只有1X,2X,3X,...,KXXKX个。

x为公约数的数字有KX个,构成的数列有(KX)N种方案。
Ansx记录以x为最大公约数的方案数,Ansx=(KX)N
Ansx会把公约数大于xx的倍数)的方案也算进去,所以必须把x的倍数为公约数的方案从Ansx中减掉

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int MAX_N  = 100000 + 5;
int n,k;
ll ksm(ll x,ll y){
	ll res=1;
	while(y){
		if(y&1) res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
ll ans[MAX_N],sum; 
int main(){
	cin>>n>>k;
	for(int x=k;x>=1;x--){
		ans[x]=ksm(k/x,n);
		for(int y=x+x;y<=k;y+=x){
			ans[x]=(ans[x]-ans[y]+mod)%mod;
		}
		sum=(sum+ans[x]*x)%mod;
	}
	cout<<sum;
	return 0;
}


D 小球入盒

问题描述

何老板有一盒小球,每个小球上都有一个整数,所有小球上的数字都不相同。一不小心,盒子被打翻了,小球散落在地。
何老板开始捡起小球装入盒子。他会执行N次操作,操作分下面两种:
操作A捡球:捡起一个小球放入盒子,该小球的数字为X(表示为A X);
操作B提问:给出一个数字Y,问当前盒中的小球,哪一个球上的数字除以Y的余数最小?输出最小余数 (表示为B Y)

对于每个提问,何老板要求你快速给出回答。 ]

N1000001X,Y300000 数据保证,第一个操作一定是捡球

输入格式

第一行,一个整数N,表示操作的总次数
接下来N行,每行描述一个操作。每个操作由一个字母和一个数字表示,字母‘A’表示操作A,字母‘B’表示操作B

输出格式

对于每个提问操作,输出一行一个整数 ,表示答案

样例输入

5
A 3
A 5
B 6
A 9
B 4

样例输出

3
1

根号分块

X的最大值为Maxnm=Maxn

m为阈值,对Y分块

情况1,Ym

暴力求解:数组g[i],表示当前集合中%i的最小值,每次询问O(1),修改O(m)

情况2,Y>m

枚举倍数:要是modY的值尽量小,可以枚举Y的倍数kY,然后查询数列中大于等于kY的最小一个数字即可,因为Y>m,所以最多枚举mK

code:一次查询O(nlogn)

#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 100000 + 5;
const int MAX_X = 300000;
int n,m,g[600 + 5];
set<int> S;
int main(){
	m=sqrt(MAX_X);
	scanf("%d",&n);
	memset(g,0x3f,sizeof(g));
	S.insert(MAX_X+1);
	for(int i=1;i<=n;i++){
		char op=getchar();
		while(op!='A' && op!='B') op=getchar();
		int x;
		scanf("%d",&x);
		if(op=='A'){
			S.insert(x);
			for(int i=1;i<=m;i++) g[i]=min(g[i],x%i);
		}
		else{
			int ans;
			if(x<=m) printf("%d\n",g[x]);
			else{
				ans=1e9;
				int tmp;
				for(int k=0;k<=MAX_X;k+=x){
					tmp=*S.lower_bound(k);
					if(tmp<=MAX_X) ans=min(ans,tmp%x);
				}
				printf("%d\n",ans);
			}
		}
	}
	return 0;
}

E 开奶茶店

问题描述

何老板到某岛国旅行。该国由编号1nn座岛构成,n1座桥将所有岛连接了起来,任意两岛都可相互到达,每座桥的长度都是1公里。
何老板特别喜欢喝奶茶,可是他发现,只有1号岛有奶茶店,这让其他岛的人们很不方便喝到奶茶。于是他决定投资,开一些奶茶店,接下来有m个事件会发生,事件有两种类型:
事件1,开店:格式(1,v),表示何老板在v号岛开了一家奶茶店;
事件2,询问:格式(2,v),表示何老板想知道,与v号岛距离最近的一家奶茶店的距离是多少?

对于每个询问,请你快速做出回答。

1n,m105

输入格式:

第一行,两个整数n,m
接下来n1行,每行两个整数u,v,表示一座桥两端的岛的编号
接下来m行,每行两个整数,表示一个事件

输出格式:

若干行,每行一个整数,依次对应一次询问的答案

样例输入

5 4
1 2
2 3
2 4
4 5
2 1
2 5
1 2
2 5

样例输出

0
3
2

根号分块。。。。

因为有询问和修改两个操作,而对答案造成影响的只有修改,所以

按修改操作次数分块,每隔n次修改操作,进行一次bfs,更新每个点到奶茶店的距离。

修改操作:

多起点bfs计算新开的n个奶茶店到每个岛的距离,更新dis数组

dis[i]记录i点岛最近奶茶店的距离。

每次bfs耗时O(n),最多bfs进行n次,共耗时O(nn)

查询操作:一次查询耗时O(nlogn)

对于第i次询问,有可能dis[v]内存储的答案并不最优,因为它可能发生在某n次修改操作的中间,还没来得及用bfs更新dis数组,所以直接暴力计算这cnt次修改操作的点与点v的距离。

code:

#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 100000 + 5;
int n,m,sqrtn;
int Last[MAX_N],Next[MAX_N<<1],End[MAX_N<<1],tot;
inline void addedge(int x,int y){
	End[++tot]=y,Next[tot]=Last[x],Last[x]=tot;
}
int dep[MAX_N],fa[MAX_N][20],dis[MAX_N],lg[MAX_N];
void dfs(int x){
	dep[x]=dep[fa[x][0]]+1;
	dis[x]=dep[x]-1;
	for(int i=1;i<=lg[dep[x]];i++) fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=Last[x];i;i=Next[i]){
		int y=End[i];
		if(y!=fa[x][0]){
			fa[y][0]=x;
			dfs(y);
		}
	}
}
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	while(dep[x]>dep[y]) x=fa[x][lg[dep[x]-dep[y]]-1];
	if(x==y) return x;
	for(int i=lg[dep[x]]-1;i>=0;i--){
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	return fa[x][0];
}
queue<int> q;
bool vis[MAX_N];
int p[MAX_N],cnt;
bool mark[MAX_N];
void bfs(){
	memset(mark,0,sizeof(mark));
	while(q.size()){
		int x=q.front();
		q.pop();
		if(mark[x]) continue;
		mark[x]=1;
		for(int i=Last[x];i;i=Next[i]){
			int y=End[i];
			if(!mark[y]){
				q.push(y);
				dis[y]=min(dis[y],dis[x]+1);
			}
		}
	}
}
inline void modify(int x){
	if(vis[x]) return;
	vis[x]=1;
	dis[x]=0;
	p[++cnt]=x;
	q.push(x);
	if(cnt==sqrtn){
		bfs();
		cnt=0;
	}
}
int main(){
	memset(dis,0x3f,sizeof(dis));
	scanf("%d%d",&n,&m);
	sqrtn=sqrt(n);
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		addedge(x,y);
		addedge(y,x);
	}
	for(int i=1;i<=n;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	dfs(1);
	vis[1]=1;
	while(m--){
		int op,x;
		scanf("%d%d",&op,&x);
		if(op==1){
			modify(x);
		}
		else{
			int ans=dis[x];
			for(int i=1;i<=cnt;i++){
				int y=p[i];
				ans=min(ans,dep[x]+dep[y]-2*dep[lca(x,y)]);
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}

总结:

今天5道题,有3根号分块,可以说是根号分块专项练习题了,但我居然只看出了一道,而且对分块的目标也选择错了。

C题都推了一半了,但到最后竟然没有考虑进去Ansx重复的情况。

根号分块目的是对于一部分数据采用一种方法解决,对于另一部分采用另一种,而来区分这两部分的信息一定是较大的,同时对于这信息一定是题目中的关键信息,比如今天的E题,有询问和修改两个操作,对于整道题,只有修改操作会对答案造成影响,所以对修改操作的次数进行分块。

总的来说,还是题做少了,经验不够

这套题应该达到的分数:100+100+100+50/100+20

posted @   Thermalrays  阅读(251)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示