0715练习赛

A 烧饼管够

问题描述

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

\(1 \leq N, M \leq 200000\)

\(1 \leq K \leq 10^9\)

\(1 \leq a_i, b_i \leq 10^9\)

输入格式

第一行,一个整数\(n\)
第二行, \(a_1,a_2,...a_n\)
第三行,一个整数\(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\)的前缀和,
暴力枚举\(suma_i\),在\(sumb\)上二分查询或\(two-pointer\)找其最大的符合条件的\((<=k-suma_i)\)
耗时\(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 加法练习

问题描述

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

\(1 \leq n,m \leq 10^5\)

\(1 \leq p,k \leq n\)

\(1 \leq a_i \leq n\)

输入格式

第一行,一个整数\(n\)
第二行, \(a_1,a_2,...,a_n\)
第三行,一个整数\(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<=\sqrt n\),进行预处理
\(f[i][j]\)表示\(p=i,k=j\)使\(p>n\)的操作数
\(f[i][j]=\left\{ \begin{matrix} f[i+a_i+j][j]+1 (a_i+i+j \leq n) \\ 1 (a_i+i+j>n) \end{matrix} \right. \)

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

\(code:\)耗时\(O(n\sqrt n)\)

#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\)的整数数列\(A_1,A_2,...,A_N\)
他发现可以构造出\(K^N\)种不同的数列。
对于其中第种数列,其公约数\(G_i=gcd(A_1,A_2,...,A_N)\)
何老板想请你帮忙计算出每个数列的公约数,并求出这些公约数之和。也就是计算\(G_1+G_2+...+G_{K^N}\)
答案可能很大\(\ mod \ (10^9+7)\)再输出

\(2 \leq N \leq 10^5\)

\(1 \leq K \leq 10^5\)

输入格式

第一行,两个整数\(N,K\)

输出格式

一个整数,表示计算结果

样例输入 1

3 2

样例输出 1

9

样例输入 2

3 200

样例输出 2

10813692

样例输入 3

100000 100000

样例输出 3

742202979

因为有\(K^N\)种不同数列,算出每个数列公约数不现实,考虑算出每个数列对答案的贡献。

又因为\(1 \leq K \leq 10^5\),我们考虑\([1,K]\)内每个数字对答案的贡献。对于数字\(x\),在\([1,K]\)内,只有\(1*X,2*X,3*X,...,\frac {K} {X}*X\)\(\frac {K} {X}\)个。

\(x\)为公约数的数字有\(\frac {K} {X}\)个,构成的数列有\((\frac {K} {X})^N\)种方案。
\(Ans_x\)记录以\(x\)为最大公约数的方案数,\(Ans_x=(\frac {K} {X})^N\)
\(Ans_x\)会把公约数大于\(x\)\(x\)的倍数)的方案也算进去,所以必须把\(x\)的倍数为公约数的方案从\(Ans_x\)中减掉

\(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\))

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

$ N≤100000, 1≤X,Y≤300000 $ 数据保证,第一个操作一定是捡球

输入格式

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

输出格式

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

样例输入

5
A 3
A 5
B 6
A 9
B 4

样例输出

3
1

根号分块

\(X\)的最大值为\(Maxn\)\(m=\sqrt {Maxn}\)

\(m\)为阈值,对\(Y\)分块

情况1,\(Y \leq m\)

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

情况2,\(Y > m\)

枚举倍数:要是\(\bmod Y\)的值尽量小,可以枚举\(Y\)的倍数\(k*Y\),然后查询数列中大于等于\(k*Y\)的最小一个数字即可,因为\(Y>m\),所以最多枚举\(m\)\(K\)

\(code:\)一次查询\(O(\sqrt n * logn)\)

#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 开奶茶店

问题描述

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

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

\(1 \leq n,m \leq 10^5\)

输入格式:

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

输出格式:

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

样例输入

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

样例输出

0
3
2

根号分块。。。。

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

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

修改操作:

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

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

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

查询操作:一次查询耗时\(O(\sqrt n * logn)\)

对于第\(i\)次询问,有可能\(dis[v]\)内存储的答案并不最优,因为它可能发生在某\(\sqrt 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\)题都推了一半了,但到最后竟然没有考虑进去\(Ans_x\)重复的情况。

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

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

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

posted @ 2021-07-15 19:03  Thermalrays  阅读(307)  评论(0)    收藏  举报