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

浙公网安备 33010602011771号