2023.03.05 模拟赛题解
平衡阵容
题意
由于每天挤奶,农民约翰的 \(N\) 奶牛始终以相同的顺序排好队。有一天,约翰农民决定组织同一些奶牛的极限飞盘游戏。为了简单起见,他从挤奶的队形中选取连续范围的奶牛玩游戏。但是,为了让所有的奶牛开心,它们不应该在高度上有太大的差距。
农民约翰已作出了 \(Q\) 个清单,记录奶牛的分组情况及它们的高度 \(height\)。
对于每个组,他希望在您的帮助下,能确定在该组中最高的牛与最低的牛之间的高度差异。
题解
很简单的题,顺带再温习一下一下线段树。
#include <stdio.h>
#define lc(id) (id<<1)
#define rc(id) (id<<1|1)
int tr[2][200005],val[50005];
inline void push_up(int id)
{
tr[0][id]=tr[0][lc(id)]<tr[0][rc(id)]?tr[0][lc(id)]:tr[0][rc(id)];
tr[1][id]=tr[1][lc(id)]>tr[1][rc(id)]?tr[1][lc(id)]:tr[1][rc(id)];
return ;
}
inline void build(int id,int l,int r)
{
if(l==r)
{
tr[0][id]=tr[1][id]=val[l];
return ;
}
int mid=l+r>>1;
build(lc(id),l,mid);
build(rc(id),mid+1,r);
push_up(id);
return ;
}
inline int max(int x,int y)
{
return x>y?x:y;
}
inline int min(int x,int y)
{
return x<y?x:y;
}
inline int maxquery(int id,int ql,int qr,int l,int r)
{
if(ql<=l&&r<=qr)
{
return tr[1][id];
}
int mid=l+r>>1,ret=-1;
if(mid>=ql)
ret=max(ret,maxquery(lc(id),ql,qr,l,mid));
if(mid<qr)
ret=max(ret,maxquery(rc(id),ql,qr,mid+1,r));
return ret;
}
inline int minquery(int id,int ql,int qr,int l,int r)
{
if(ql<=l&&r<=qr)
{
return tr[0][id];
}
int mid=l+r>>1,ret=10000005;
if(mid>=ql)
ret=min(ret,minquery(lc(id),ql,qr,l,mid));
if(mid<qr)
ret=min(ret,minquery(rc(id),ql,qr,mid+1,r));
return ret;
}
int main()
{
int l,r,i,n,q;scanf("%d %d",&n,&q);
for(i=1;i<=n;++i)
{
scanf("%d",val+i);
}
build(1,1,n);
while(q--)
{
scanf("%d %d",&l,&r);
printf("%d\n",maxquery(1,l,r,1,n)-minquery(1,l,r,1,n));
}
return 0;
}
滑板鞋
题意
你在魅力之都购买了一双时尚的滑板鞋,你非常兴奋地到处摩擦!
\(\textbf{Smart}\) 很想问一个问题:按照你的行动方式,你从某个结点摩擦(移动)\(K\) 步后能到的目的地。
这显然是一个很简单的问题,但是 \(\textbf{Smart}\) 总是问个不停,所以你决定写一个程序回答他的询问。
题解
此题中有 \(k\le 10^{18}\),暴力不能过,这种情况通常考虑 \(\log\) 做法。把 \(\textbf{LCA}\) 那一套拿过来。
\(f_{i,j}\) 表示 \(i\) 滑动 \(2^j\) 步后的目的地。显然有 \(f_{i,j}=f{f_{i,j-1},j-1}\)。
预处理 \(O(n\log V)\),单次询问 \(O(\log V)\),总复杂度 \(O((n+m)\log V)\)。
#include <stdio.h>
int f[100005][65],a[100005];
int main()
{
int n,m,i,lg,base,cnt;
long long k;
scanf("%d %d",&n,&m);
for(i=1;i<=n;++i)
{
scanf("%d",a+i);
f[i][0]=a[i];
}
for(lg=1;lg<65;++lg)
{
for(i=1;i<=n;++i)
f[i][lg]=f[f[i][lg-1]][lg-1];
}
while(m--)
{
cnt=0;
scanf("%d %lld",&base,&k);
while(k)
{
if(k&1)
{
base=f[base][cnt];
}
k>>=1;
++cnt;
}
printf("%d\n",base);
}
return 0;
}
图
题意
有一个 \(n\) 个点 \(n\) 条边的有向图,每条边为 \(\{i,f(i),w(i)\}\),意思是 \(i\) 指向 \(f(i)\) 的边权为 \(w(i)\) 的边,现在 \(\textbf{Smart}\) 想知道,对于每个点 \(s_i\) 和 \(m_i\)。
- \(s_i\):从 \(i\) 出发走 \(k\) 条边,路径权值和。
- \(m_i\):从 \(i\) 出发走 \(k\) 条边,路径权值最小值。
题解
感觉和前一题没有本质区别,典得不能再典。
#include <stdio.h>
#include <algorithm>
using std::min;
long long f[3][100005][65],a[100005];
int vec[65];
int cnt=0;
int main()
{
int n,i,lg,base;
long long k,s,m;
scanf("%d %lld",&n,&k);
for(i=0;i<n;++i)
{
scanf("%d",a+i);
f[2][i][0]=a[i];
}
for(i=0;i<n;++i)
{
scanf("%d",a+i);
f[0][i][0]=f[1][i][0]=a[i];
}
for(lg=1;lg<65;++lg)
{
for(i=0;i<n;++i)
{
f[0][i][lg]=f[0][i][lg-1]+f[0][f[2][i][lg-1]][lg-1];
f[1][i][lg]=min(f[1][i][lg-1],f[1][f[2][i][lg-1]][lg-1]);
f[2][i][lg]=f[2][f[2][i][lg-1]][lg-1];
}
}
while(k)
{
vec[++cnt]=std::__lg(k&-k);
k-=k&-k;
}
for(auto __base=0;__base<n;++__base)
{
base=__base;
s=0;m=1ll<<60;
for(i=1;i<=cnt;++i)
{
s+=f[0][base][vec[i]];
m=min(f[1][base][vec[i]],m);
base=f[2][base][vec[i]];
}
printf("%lld %lld\n",s,m);
}
return 0;
}
最小线段覆盖
题意
\(n\) 条线段 \([l,r]\),对于每次询问 \([ql,qr]\),问至少几条线段才能覆盖。
题解
考虑倍增。
套路地,\(f_{i,j}\) 表示从 \(i\) 条 \(j\) 条线段的最右的最右端。然后套路地转移,做完了。
#include <stdio.h>
int n,m,l,r,i,j,ans;
int f[500005][65];
const int V=500000;
inline int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&l,&r);
f[l][0]=max(f[l][0],r);
}
for(i=1;i<=V;++i)
f[i][0]=max(f[i][0],f[i-1][0]);
for(j=1;j<65;++j)
for(i=0;i<=V;++i)
f[i][j]=f[f[i][j-1]][j-1];
while(m--)
{
scanf("%d %d",&l,&r);
ans=1;
for(i=30;i>=0;i--)
if(f[l][i]<r)
{
ans+=1<<i;
l=f[l][i];
}
if(f[l][0]>=r)
printf("%d\n",ans);
else
puts("-1");
}
return 0;
}
GCD 查询
题意
给出 \(\{a_n\}\) 和 \(\{x_m\}\),对于每个 \(x_i\) 求满足 \(1\le l\le r\le n\) 且 \(\gcd\{a_l,a_{l+1},\cdots,a_r\}=x_i\) 的区间 \([l,r]\) 个数。
题解
困难的。
首先我们需要观察到一个性质,\(K=\gcd\{a_l,a_{l+1},\cdots,a_r\}\) 至多只有 \(n\log V\) 种。
简要证明:
- 显然有 \(x\ge \gcd\{x,y\}\text{ }(x,y\in \mathrm {N})\)。
- 若 \(\gcd\{x,y\}<x\),那么必定 \(\gcd\{x,y\}\le \dfrac x 2\)
- 那么对于任意一个 \(i\),\(i\) 作为 \(l\) 时,\(K\) 至多会有 \(\log_2 a_i\) 种。
由此该性质得到了就简单的证明。其实实际上应该远远跑不到 \(n\log V\)。
然后考虑如何统计答案。
套路地枚举左端点,由于 \(gcd\) 不升,那么就可以二分右端点。
方便叙述,设 \(G(l,r)=\gcd\{a_l,a_{l+1},\cdots,a_r\}\)。
- 若 \(G(i,l)\not=G(i,l+1)\),则对答案 \(G(i,l)\) 产生 \(l-i+1\) 的贡献。
- 若 \(G(i,l-1)\not=G(i,l)=G(i,r)\not=G(i,r+1)\),则对答案 \(G(i,l-1)\) 产生 \(r-l+1\) 的贡献。
- 以此类推。
记录答案的话,由于此题 \(a_i\le 10^9\),就用 std::map
吧。
分析一下复杂度,\(\gcd\) 是跑不满的 \(O(\log V)\),一次二分是 \(O(\log^2 n)\),总复杂度 \(O(n\log^2 n\log V+q\log (n\log V))\),大概是 \(O(n\log ^3 n)\) 的样子吧。
本来用线段树实现,但是似乎常数太大,大数据会超时,改 ST 表实现。
#include <map>
#include <stdio.h>
#define lc(id) (id<<1)
#define rc(id) (id<<1|1)
std::map<int,long long> rem;
int lg[100005],st[100005][25];
//int tr[400005],val[100005];
inline int gcd(int x,int y)
{
return !y?x:gcd(y,x%y);
}
/*
inline void push_up(int id)
{
tr[id]=gcd(tr[lc(id)],tr[rc(id)]);
return ;
}
inline void build(int id,int l,int r)
{
if(l==r)
{
tr[id]=val[l];
return ;
}
int mid=l+r>>1;
build(lc(id),l,mid);
build(rc(id),mid+1,r);
push_up(id);
return ;
}
inline int query(int id,int ql,int qr,int l,int r)
{
if(ql<=l&&r<=qr)
return tr[id];
int mid=l+r>>1,lans,rans;
lans=rans=0;
if(ql<=mid)
lans=query(lc(id),ql,qr,l,mid);
if(mid<qr)
rans=query(rc(id),ql,qr,mid+1,r);
return gcd(lans,rans);
}
*/
inline int query(int l, int r) {
static int _lg;
_lg=lg[r-l+1];
return gcd(st[l][_lg],st[r-(1<<_lg)+1][_lg]);
}
int main()
{
int nxt,j,n,i,now,l,q,x,r,mid;scanf("%d",&n);
for(i=1;i<=n;++i)
scanf("%d",st[i]);
for(i=2;i<=100000;++i)
lg[i]=lg[i>>1]+1;
for(j=1;(1<<j)<=n;++j)
for(i=1;i+(1<<j)-1<=n;++i)
st[i][j]=gcd(st[i][j-1],st[i+(1<<j-1)][j-1]);
// build(1,1,n);
// for(i=1;i<=n;++i,puts(""))
// for(int j=i;j<=n;++j)
// printf("%d ",query(1,i,j,1,n));
for(i=1;i<=n;++i)
{
nxt=i;
while(nxt<=n)
{
now=query(i,nxt);
l=nxt;r=n;
while(l<=r)
{
mid=l+r>>1;
if(query(i,mid)!=now)
r=mid-1;
else
l=mid+1;
}
rem[now]+=l-nxt;
nxt=l;
}
}
scanf("%d",&q);
while(q--)
{
scanf("%d",&x);
printf("%lld\n",rem[x]);
}
}