【原創】2019.09.01模擬賽 斐波那契 數顏色

小C的模擬賽

斐波那契(fibonacci)

【題目描述】

CC 養了一些很可愛的兔子。
有一天,小 CC 突然發現兔子們都是嚴格按照偉大的數學家斐波那契提出的模型來進行繁衍:一對兔子從出生後第二個月起,每個月剛開始的時候都會產下一對小兔子。我們假定,在整個過程中兔子不會出現任何意外。
CC把兔子按出生順序,把兔子們從 11 開始標號,並且小 CC 的兔子都是 11 號兔子和 11號兔子的後代。如果某兩對兔子是同時出生的,那麼小CC 會將父母標號更小的一對優先標號。
如果我們把這種關係用圖畫下來,前六個月大概就是這樣的:
在這裏插入圖片描述
其中,一個箭頭 ABA →B 表示 AABB 的祖先,相同的顏色表示同一個月出生的兔子。
爲了更細緻地瞭解兔子們是如何繁衍的,小 CC 找來了一些兔子,並且向你提出了 mm 個問題:她想知道關於每兩對兔子 aia_ibib_i ,他們的最近公共祖先是誰。你能幫幫小 CC 嗎?
一對兔子的祖先是這對兔子以及他們父母(如果有的話)的祖先,而最近公共祖先是指兩對兔子所共有的祖先中,離他們的距離之和最近的一對兔子。比如,5577 的最近公共祖先是221122 的最近公共祖先是116666 的最近公共祖先是 66

【輸入格式】

從文件 fibonacci.infibonacci.in 中讀入數據。
輸入第一行,包含一個正整數 mm
輸入接下來 mm 行,每行包含 22 個正整數,表示 aia_ibib_i

【輸出格式】

輸出到文件 fibonacci.outfibonacci.out 中。
輸入一共 mm 行,每行一個正整數,依次表示你對問題的答案。
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】

見選手目錄下的 fibonacci/fibonacci2.infibonacci/fibonacci2.ansfibonacci/fibonacci2.in 與 fibonacci/fibonacci2.ans

【數據範圍與約定】

子任務會給出部分測試數據的特點。如果你在解決題目中遇到了困難,可以嘗試只解
決一部分測試數據。
每個測試點的數據規模及特點如下表:
特殊性質 1:保證 aia_i,bib_i 均爲某一個月出生的兔子中標號最大的一對兔子。例如,對
於前六個月,標號最大的兔子分別是 1,2,3,5,8,131, 2, 3, 5, 8, 13
特殊性質 2:保證 aibi1|a_i − b_i| ≤ 1

在這裏插入圖片描述

【分析】

以下考場分析:

發現10^12次方範圍裏面只有不到60個斐波那契數,然後就激起了我的暴力之魂。 對於每個數n,我們找到它第一個大於的斐波那契數fif_i(跑一下upper_bound),然後它的父親就是n-fif_i

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

我算了算時間,也許不用upper_bound都可以輕鬆過。之前還考慮了一邊往上跑一邊縮小二分的範圍呢,算了吧。

老師讓我們寫對拍,可是這大暴力……
只是測試了一下極限數據的速度,在我們這個不是很好的機子上跑了快1.8秒。於是再加上一邊往上跑一邊縮小二分的範圍,還是快1.8秒。

沒救了。


其實既然想到可以一邊跑一邊縮小二分的範圍就可以想到直接倒着枚舉,Θ(n)\Theta(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 ark)
{
	int l=1,r=59,mid;
	while(l<=r)
		if(fib[mid=((l+r)>>1)]<ark) l=mid+1;
		else r=mid-1;
	return l;
}

int main()
{
	freopen("fibonacci.in","r",stdin);
	freopen("fibonacci.out","w",stdout);
	for(int i=1;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 把她標號從 11nnnn只兔子排成長長的一排,來給他們喂胡蘿蔔吃。
排列完成後,第 𝑖 只兔子的顏色是 aia_i
俗話說得好,“蘿蔔青菜,各有所愛”。小 C 發現,不同顏色的兔子可能有對胡蘿蔔的不同偏好。比如,銀色的兔子最喜歡吃金色的胡蘿蔔,金色的兔子更喜歡吃胡蘿蔔葉子,而綠色的兔子卻喜歡吃酸一點的胡蘿蔔……爲了滿足兔子們的要求,小 C 十分苦惱。所以,爲了使得胡蘿蔔喂得更加準確,小 C 想知道在區間 [lj,rj][l_j, r_j]裏有多少隻顏色爲cjc_j的兔子。
不過,因爲小 C 的兔子們都十分地活躍,它們不是很願意待在一個固定的位置;與此同時,小 C 也在根據她知道的信息來給兔子們調整位置。所以,有時編號爲 xjx_jxj+1x_j+1 的兩隻兔子會交換位置。
小 C 被這一系列麻煩事給難住了。你能幫幫她嗎?

【輸入格式】

從文件color.incolor.in 中讀入數據。
輸入第 1 行兩個正整數 n,mn,m
輸入第 2 行 nn 個正整數,第 ii 個數表示第 ii只兔子的顏色 aia_i
輸入接下來 mm 行,每行爲以下兩種中的一種:“1 ljl_j rjr_j cjc_j” :詢問在區間 [lj,rjl_j,r_j] 裏有多少隻顏色爲 cjc_j 的兔子;“2 xjx_j”: xjx_jxj+1x_j + 1 兩隻兔子交換了位置。

【輸出格式】

輸出到文件color.outcolor.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分轉向拼盤,爲什麼不動態開點呢?
這不是動態開點板子題嗎?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章