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]);
	}
}
posted @ 2023-03-05 14:00  Syara  阅读(103)  评论(0编辑  收藏  举报