【原创】2019.09.01模拟赛 斐波那契 数颜色

小C的模拟赛

斐波那契(fibonacci)

【题目描述】

C C C 养了一些很可爱的兔子。
有一天,小 C C C 突然发现兔子们都是严格按照伟大的数学家斐波那契提出的模型来进行繁衍:一对兔子从出生后第二个月起,每个月刚开始的时候都会产下一对小兔子。我们假定,在整个过程中兔子不会出现任何意外。
C C C把兔子按出生顺序,把兔子们从 1 1 1 开始标号,并且小 C C C 的兔子都是 1 1 1 号兔子和 1 1 1号兔子的后代。如果某两对兔子是同时出生的,那么小 C C C 会将父母标号更小的一对优先标号。
如果我们把这种关系用图画下来,前六个月大概就是这样的:
在这里插入图片描述
其中,一个箭头 A → B A →B AB 表示 A A A B B B 的祖先,相同的颜色表示同一个月出生的兔子。
为了更细致地了解兔子们是如何繁衍的,小 C C C 找来了一些兔子,并且向你提出了 m m m 个问题:她想知道关于每两对兔子 a i a_i ai b i b_i bi ,他们的最近公共祖先是谁。你能帮帮小 C C C 吗?
一对兔子的祖先是这对兔子以及他们父母(如果有的话)的祖先,而最近公共祖先是指两对兔子所共有的祖先中,离他们的距离之和最近的一对兔子。比如, 5 5 5 7 7 7 的最近公共祖先是 2 2 2 1 1 1 2 2 2 的最近公共祖先是 1 1 1 6 6 6 6 6 6 的最近公共祖先是 6 6 6

【输入格式】

从文件 f i b o n a c c i . i n fibonacci.in fibonacci.in 中读入数据。
输入第一行,包含一个正整数 m m m
输入接下来 m m m 行,每行包含 2 2 2 个正整数,表示 a i a_i ai b i b_i bi

【输出格式】

输出到文件 f i b o n a c c i . o u t fibonacci.out fibonacci.out 中。
输入一共 m m m 行,每行一个正整数,依次表示你对问题的答案。
1
2
5 7
3 4 6
8 11 12
9
13
10

【样例 1 输入】

5
1 1
2 3
5 7
7 13
4 12

【样例 1 输出】

1
1
2
2
4

【样例 2】

见选手目录下的 f i b o n a c c i / f i b o n a c c i 2. i n 与 f i b o n a c c i / f i b o n a c c i 2. a n s fibonacci/fibonacci2.in 与 fibonacci/fibonacci2.ans fibonacci/fibonacci2.infibonacci/fibonacci2.ans

【数据范围与约定】

子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解
决一部分测试数据。
每个测试点的数据规模及特点如下表:
特殊性质 1:保证 a i a_i ai, b i b_i bi 均为某一个月出生的兔子中标号最大的一对兔子。例如,对
于前六个月,标号最大的兔子分别是 1 , 2 , 3 , 5 , 8 , 13 1, 2, 3, 5, 8, 13 1,2,3,5,8,13
特殊性质 2:保证 ∣ a i − b i ∣ ≤ 1 |a_i − b_i| ≤ 1 aibi1

在这里插入图片描述

【分析】

以下考场分析:

发现10^12次方范围里面只有不到60个斐波那契数,然后就激起了我的暴力之魂。 对于每个数n,我们找到它第一个大于的斐波那契数 f i f_i fi(跑一下upper_bound),然后它的父亲就是n- f i f_i fi

比如说9,它是8+1,说明它是第1个兔子生的。
比方说13,它是8+5,说明它是第5个兔子生的。

我算了算时间,也许不用upper_bound都可以轻松过。之前还考虑了一边往上跑一边缩小二分的范围呢,算了吧。

老师让我们写对拍,可是这大暴力……
只是测试了一下极限数据的速度,在我们这个不是很好的机子上跑了快1.8秒。于是再加上一边往上跑一边缩小二分的范围,还是快1.8秒。

没救了。


其实既然想到可以一边跑一边缩小二分的范围就可以想到直接倒着枚举, Θ ( n ) \Theta(n) Θ(n)就解决了。
我太关注于每一步了,没有看步与步之间的关系。

【代码】

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

#define ll long long
inline void Read(ll &p)
{
	p=0;
	char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0'  && c<='9') 
		p=p*10+c-'0',c=getchar();
}

inline void Read(int &p)
{
	p=0;
	char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0'  && c<='9') 
		p=p*10+c-'0',c=getchar();
}

const int MAXF=80;
int m,cp,cq;
ll u,v,p[MAXF],q[MAXF],fib[MAXF]={1,1};

inline int my_lower_bound(ll jzm)
{
	int l=1,r=59,mid;
	while(l<=r)
		if(fib[mid=((l+r)>>1)]<jzm) l=mid+1;
		else r=mid-1;
	return l;
}

int main()
{
	freopen("fibonacci.in","r",stdin);
	freopen("fibonacci.out","w",stdout);
	for(int i=2;i<=60;i++) fib[i]=fib[i-1]+fib[i-2];
	Read(m);
	while(m--) 
	{
		for(int i=0;i<=65;i++) p[i]=q[i]=0;
		Read(u),Read(v),p[cp=1]=u,q[cq=1]=v;
		while(u!=1) p[++cp]=(u-=fib[my_lower_bound(u)-1]); 
		while(v!=1) q[++cq]=(v-=fib[my_lower_bound(v)-1]);
		
		int ans=1;
		for(int i=1;i<=max(cp,cq);i++) if(p[cp-i+1]==q[cq-i+1]) ans=i; else break;
		printf("%lld\n",p[cp-ans+1]);
	}
}

期望得分:100
实际得分:100
也不需要优化了……

数颜色(color)

【题目描述】

小 C 的兔子不是雪白的,而是五彩缤纷的。每只兔子都有一种颜色,不同的兔子可能有
相同的颜色。小 C 把她标号从 1 1 1 n n n n n n只兔子排成长长的一排,来给他们喂胡萝卜吃。
排列完成后,第 ? 只兔子的颜色是 a i a_i ai
俗话说得好,“萝卜青菜,各有所爱”。小 C 发现,不同颜色的兔子可能有对胡萝卜的不同偏好。比如,银色的兔子最喜欢吃金色的胡萝卜,金色的兔子更喜欢吃胡萝卜叶子,而绿色的兔子却喜欢吃酸一点的胡萝卜……为了满足兔子们的要求,小 C 十分苦恼。所以,为了使得胡萝卜喂得更加准确,小 C 想知道在区间 [ l j , r j ] [l_j, r_j] [lj,rj]里有多少只颜色为 c j c_j cj的兔子。
不过,因为小 C 的兔子们都十分地活跃,它们不是很愿意待在一个固定的位置;与此同时,小 C 也在根据她知道的信息来给兔子们调整位置。所以,有时编号为 x j x_j xj x j + 1 x_j+1 xj+1 的两只兔子会交换位置。
小 C 被这一系列麻烦事给难住了。你能帮帮她吗?

【输入格式】

从文件 c o l o r . i n color.in color.in 中读入数据。
输入第 1 行两个正整数 n , m n,m n,m
输入第 2 行 n n n 个正整数,第 i i i 个数表示第 i i i只兔子的颜色 a i a_i ai
输入接下来 m m m 行,每行为以下两种中的一种:“1 l j l_j lj r j r_j rj c j c_j cj” :询问在区间 [ l j , r j l_j,r_j lj,rj] 里有多少只颜色为 c j c_j cj 的兔子;“2 x j x_j xj”: x j x_j xj x j + 1 x_j + 1 xj+1 两只兔子交换了位置。

【输出格式】

输出到文件 c o l o r . o u t color.out color.out 中。
对于每个 1 操作,输出一行一个正整数,表示你对于这个询问的答案。

【样例 1 输入】

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

【样例 1 输出】

1
2
2
3

【样例 1 说明】

前两个 1 操作和后两个 1 操作对应相同;在第三次的 2 操作后,3 号兔子和 4 号兔子
交换了位置,序列变为 1 2 2 3 3 3。

【样例 2】

见选手目录下的 color/color2.in 与 color/color2.ans。

【数据范围】

子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试只解
决一部分测试数据。
对于所有测试点,有 1 ≤ ?? < ?? ≤ ?, 1 ≤ ?? < ?。
每个测试点的数据规模及特点如下表:
在这里插入图片描述
特殊性质 1:保证对于所有操作 1,有 |?? − ??| ≤ 20 或 |?? − ??| ≥ ? − 20。
特殊性质 2:保证不会有两只兔子相同颜色的兔子。

【分析&代码】

考场分析:

一看到这道题,啊,是莫队。
啊,动态莫队。
等一下,普通的莫队是不是不带修改的来着?
如果我没记错的话,普通的莫队应该处理不了带修改的东西吧。
那难道像整体二分一样,整体莫队?那算了吧。
呀,不对啊,它是交换,而且是相邻交换,好像是可以分块优化的吧,这是非常显然的分块优化吧?!
真的耶!

于是我开始考虑拼盘拿部分分。
前6个点以及还有2个颜色<=10的点:考虑对于每种颜色建一个树状数组,单点修改区间求和。线段树也可以,时间够。于是40分get。
除此以外还有2个特殊性质1的点,它满足每次询问的区间的长度要么<=20,要么>=n-20。<=20可以直接枚举,>=n-20的先对每个数开一个桶,然后剩下20个的枚举,从桶里拿走数字。
除此以外还有2个特殊性质2的点,它满足没有颜色相同的兔子,于是拿个桶就好了。
60分get。

拼盘的时候,如果n<=1000,就开1000个[1,1000]的树状数组;如果色号<=10,就开10个[1,300000]的树状数组;如果所有颜色都只出现过一次,就直接桶一桶;否则就暴力枚举。

你说还有两个点不带修改的,为什么不写普通的莫队呢?
……
你认为我为什么要拼盘?
我也尝试过直接分块优化搞100分……
……
尝试过的。

代码简单的不行,写了四份对拍,检查了数据范围和内存限制,时间肯定没问题,60分应该稳了。

考场代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

void Read(int &p)
{
	p=0;
	char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0'  && c<='9') 
		p=p*10+c-'0',c=getchar();
}

#define lowbit(i) ((i)&(-(i)))

const int MAXN=302030,MINN=1024,MINC=15;
int n,m,opt,x,y,z,t,arr[MAXN];
//拼盘1:色号<=1000 兔子<=1000
int bit[MINN][MINN];
//拼盘2:色号<=10 兔子<=300000
int mxc,tre[MINC][MAXN];
//拼盘3&4:用桶暴力搞
int mxs,ans,cnt[MAXN],plc[MAXN];

inline void update_bit(int crl,int pos,int val)
{
	while(pos<=n) bit[crl][pos]+=val,pos+=lowbit(pos);
}

inline int getsum_bit(int crl,int pos)
{
	int sum=0;
	while(pos>0) sum+=bit[crl][pos],pos-=lowbit(pos);
	return sum;
}

inline void update_tre(int crl,int pos,int val)
{
	while(pos<=n) tre[crl][pos]+=val,pos+=lowbit(pos);
}

inline int getsum_tre(int crl,int pos)
{
	int sum=0;
	while(pos>0) sum+=tre[crl][pos],pos-=lowbit(pos);
	return sum;
}

inline void my_swap(int &x,int &y)
{
	t=x,x=y,y=t;
}

int main()
{
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	Read(n),Read(m);
	for(int i=1;i<=n;i++) Read(arr[i]),mxs=max(mxs,++cnt[arr[i]]),mxc=max(mxc,arr[i]);
	
	if(n<=1000)
	{
		for(int i=1;i<=n;i++) update_bit(arr[i],i,1);
		while(m--)
		{
			Read(opt);
			if(opt==1) Read(x),Read(y),Read(z),printf("%d\n",getsum_bit(z,y)-getsum_bit(z,x-1));
			else Read(x),update_bit(arr[x],x,-1),update_bit(arr[x],x+1,1),update_bit(arr[x+1],x+1,-1),update_bit(arr[x+1],x,1),my_swap(arr[x],arr[x+1]);
		}
	}
	else if(mxc<=10)
	{
		for(int i=1;i<=n;i++) update_tre(arr[i],i,1);
		while(m--)
		{
			Read(opt);
			if(opt==1) Read(x),Read(y),Read(z),printf("%d\n",getsum_tre(z,y)-getsum_tre(z,x-1));
			else Read(x),update_tre(arr[x],x,-1),update_tre(arr[x],x+1,1),update_tre(arr[x+1],x+1,-1),update_tre(arr[x+1],x,1),my_swap(arr[x],arr[x+1]);
		}
	}
	else if(mxs==1)
	{
		for(int i=1;i<=n;i++) plc[arr[i]]=i;
		while(m--)
		{
			Read(opt);
			if(opt==1) Read(x),Read(y),Read(z),printf("%d\n",(x<=plc[z] && plc[z]<=y));
			else Read(x),my_swap(plc[arr[x]],plc[arr[x+1]]),my_swap(arr[x],arr[x+1]);
		}
	}
	else 
	{
		while(m--)
		{
			Read(opt);
			if(opt==1)
			{
				ans=0,Read(x),Read(y),Read(z);
				if(y-x<=n-(y-x))
				{
					for(int i=x;i<=y;i++) ans+=(arr[i]==z);
					printf("%d\n",ans);
				}
				else
				{
					for(int i=1;i<x;i++) ans+=(arr[i]==z);
					for(int i=y+1;i<=n;i++) ans+=(arr[i]==z);
					printf("%d\n",cnt[z]-ans);
				}
			}
			else Read(x),my_swap(arr[x],arr[x+1]);
		}
	}
}

这是我第一次考场代码没删注释……
还有4个对拍真的开心。


这个我就是逊啦!
因为担心树状数组/线段树开不下所以放弃100分转向拼盘,为什么不动态开点呢?
这不是动态开点板子题吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值