gmoj 6847. 【2020.11.03提高组模拟】通往强者之路

题目

https://gmoj.net/senior/#main/show/6847

比赛做法

比赛时我想打表找规律,然后发现若把a分成区间 [ 0 , n ] , [ n + 1 , 2 n + 1 ] , [ 2 n + 2 , 3 n + 2 ] , ⋯ [0,n],[n+1,2n+1],[2n+2,3n+2],\cdots [0,n],[n+1,2n+1],[2n+2,3n+2],

观察每一个区间,可以发现:若第 0 个为 n-1,第 n 个也为 n-1 ;否则第 n 个是 n。
比较第 i 个和第 i+1 个区间,可以发现以下规律:

  1. 每一个左边是 n 的 n-1 都向左移动了1步(和左边的那个数字交换位置,如果 n-1 下标为0,那么它会消失);
  2. 如果 n-1 左边是 n+1,那么它们两个就会变成n;
  3. 其它 n+1 和 n 都不变。

那么维护出 n-1 会和哪个 n+1 在什么时间相遇就好了。
但是实现出锅了,最后只好把暴力和 O ( n log ⁡ 2 n ) O(n\log_2 n) O(nlog2n)的代码相结合,TLE 10。

正确做法

如果把a分成长度为n的区间,可以发现不同的规律(令当前区间编号为 i ):

  1. 每一个 n+1 每次都会向右移动一步,如果它在区间 i 的末尾,它就会出现在 i+2 的开头,不会在 i+1 中出现;
  2. n+1 和 n-1 相遇会变成两个n;
  3. 其它 n-1 和 n 都不变。

由于这个 n+1 的消失实在鬼畜,我们不妨在每个区间末尾加上一个元素(初始为 n),那么第 x 个元素在一次变换后位置就是 ( x + 1 ) m o d    ( n + 1 ) (x+1)\mod (n+1) (x+1)mod(n+1)

结合我比赛的做法,得到一种解法:

  1. 算出 a n a_n an(因为 m o d    ( n + 1 ) \mod (n+1) mod(n+1)后有可能会访问到 a n a_n an);
  2. 算出每一个 n+1 在什么时候会和哪一个 n-1 相遇,这个 O ( n ) O(n) O(n)扫一下就好了,相遇时间为 l ⋅ n l\cdot n ln(令 l l l为它们之间的长度,其实这个时间表示的是它们消失的那个区间的开头)。注意,这里要用栈来储存上一个 n-1 而不能直接并查集!
  3. 给会和 n-1 相遇的 n+1 按相遇时间排序,给询问排序;
  4. 对于每一个询问x,先把会消失的 n + 1 , n − 1 n+1,n-1 n+1,n1全部变成 n n n,然后考虑当前位置上的数是什么:
    1). 若 a x m o d    n = n − 1 a_{x\mod{n}}=n-1 axmodn=n1,说明它是 n-1(因为 n-1 是不动的,而且这个 n-1 还没有消失);
    2). 若 a x m o d    ( n + 1 ) = n + 1 a_{x\mod{(n+1)}}=n+1 axmod(n+1)=n+1,说明它是 n+1 (这是用了我比赛时的规律,此时 n+1 才是不变的,n-1 每次向左移动一步);
    3). 否则它就是 n 。

时间复杂度 O ( n log ⁡ 2 n ) O(n\log_2 n) O(nlog2n)

CODE

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
#define N 100005
int a[N],l[N],ans[N],sta[N];
struct node{int id;ll time;}arr[N],qry[N];
bool cmp(node x,node y){return x.time<y.time;}
inline char gc()
{
	static char buf[100005],*l=buf,*r=buf;
	return l==r&&(r=(l=buf)+fread(buf,1,100005,stdin),l==r)?EOF:*l++;
}
inline void read(int &k)
{
	char ch;while(ch=gc(),ch<48||ch>57);k=ch-48;
	while(ch=gc(),ch>=48&&ch<=57) k=k*10+ch-48;
}
inline void read_(ll &k)
{
	char ch;while(ch=gc(),ch<48||ch>57);k=ch-48;
	while(ch=gc(),ch>=48&&ch<=57) k=k*10+ch-48;
}
int main()
{
	freopen("way.in","r",stdin);
	freopen("way.out","w",stdout);
	int t,n,q,top,cnt,tmp;
	scanf("%d",&t);
	while(t--)
	{
		memset(l,-1,sizeof l);
		cnt=top=0,read(n),read(q);
		for(int i=0;i<n;++i) read(a[i]);a[n]=a[0]<n?n-1:n;
		for(int i=1;i<=q;++i) read_(qry[i].time),qry[i].id=i;
		for(int i=n-1;i>=0;--i)
		{
			if(a[i]==n-1) sta[++top]=i;
			if(a[i]==n+1&&top) l[i]=sta[top--];
		}
		for(int i=n-1;i>=0&&top;--i) if(a[i]==n+1&&l[i]==-1) l[i]=sta[top--];
		for(int i=0;i<=n;++i) if(l[i]!=-1)
			arr[++cnt]=(node){i,(l[i]>i?l[i]-i:l[i]+n+1-i)*(ll)n};
		sort(arr+1,arr+cnt+1,cmp);
		sort(qry+1,qry+q+1,cmp);
		for(int i=1,j=1;i<=q;++i)
		{
			while(j<=cnt&&arr[j].time<=qry[i].time) a[arr[j].id]=a[l[arr[j].id]]=n,++j;
			if(a[qry[i].time%n]==n-1) ans[qry[i].id]=n-1;
			else ans[qry[i].id]=n+(a[qry[i].time%(n+1)]==n+1);
		}
		for(int i=1;i<q;++i) printf("%d ",ans[i]);
		printf("%d\n",ans[q]);
	}
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值