1. 程式人生 > 實用技巧 >「考試」大假期集訓模擬13

「考試」大假期集訓模擬13

題目簡述

T1 斐波那契(fibonacci)

考場

考試時不會做,所以藉著特殊條件騙了60分,其實有些僥倖了吧qaq,不過總比以前想不出正解就無計可施強多了……
其實正解也是利用了特殊條件來優化,所以特殊條件有時候其實挺有提示性的

正解

首先找一下規律,發現編號為\(2,3,4,5,6,7,8,9\cdots\)的兔子,祖先為\(1,1,1,2,1,2,3,1\cdots\),這裡的規律是:一個兔子的祖先是他的編號減去小於他編號的最大菲波那契數
範圍是\(1e12\),這個範圍裡的菲波那契數只有60個,所以打出表來,再二分查一下即可,寫\(lower_bound\)似乎會被卡,所以以後多手寫二分吧qaq
再有就是利用題裡的特殊情況得到兩個優化:(其實不加也是能過的)

  1. 如果當前跳到的兩數差小於1,就有兩種情況,如果兩數相等則他們是答案,如果兩數差為\(1\)則答案為\(1\)
  2. 如果當前的數是\(fib_i\),他的父親為\(fib_{i-2}\)
    code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define N 75

ll fib[N],cnt=0;
ll m,a,b;

ll find(ll x)
{
	ll l=1,r=cnt,m;
	while(l<r)
	{
		m=(l+r)>>1;
		if(fib[m]>=x)r=m;
		else l=m+1;
	}
	return l;
}

ll tmp;

int main()
{
	ll aa=0,bb=1,cc=0;
	while(cc<1000000000000)
	{
		cc=aa+bb;
		fib[++cnt]=cc;
		aa=bb,bb=cc;
	}
	scanf("%lld",&m);
	ll flag=0;
	for(int i=1;i<=m;i++)
	{
		flag=0;
		scanf("%lld%lld",&a,&b);
		while(a!=b)
		{
			
			if(abs(a-b)<=1)
			{
				if(a==b)printf("%lld\n",a);
				else printf("1\n");
				flag=1;
				break;
			}
			if(a<b)swap(a,b);
			a=a-fib[find(a)-1];
		}
		if(!flag)printf("%lld\n",a);
	}
	return 0;
}
##T2 數顏色
###考場
聯想到了上次的string,於是寫起了線段樹,然後發現複雜度不對,只有30分……
###正解
使用$vector$
查詢:給每個顏色開一個$vector$,將顏色出現的位置插入其中,每次查詢某個顏色就在這個顏色的$vector$裡二分找出$l$的$lower_bound$和$r$的$upper_bound-1$,則答案為$r-l+1$
($Ps:$其實插入的位置是有序的,所以可以放心二分)
修改:先找到兩個位置在$vector$裡的位置,然後直接修改,因為交換前後兩個,所以前面++,後面--就行了
似乎是第一次碰到這樣的題,之前都沒怎麼碰過$vector,map$之類的東西……似乎用這些有時會很方便,以後要多看看qaq
code:
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define N 300005

ll n,m;
ll a[N];
vector<ll> g[N];
ll t,l,r,c,x;

int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)g[a[i]].push_back(i);
	for(int i=1;i<=m;i++)
	{
		scanf("%lld",&t);
		if(t==1)
		{
			scanf("%lld%lld%lld",&l,&r,&c);
			ll t1=lower_bound(g[c].begin(),g[c].end(),l)-g[c].begin();
			ll t2=upper_bound(g[c].begin(),g[c].end(),r)-g[c].begin()-1;
			printf("%lld\n",t2-t1+1);
		}
		else if(t==2)
		{
			scanf("%lld",&x);
			if(a[x]!=a[x+1])
			{
				ll t1=lower_bound(g[a[x]].begin(),g[a[x]].end(),x)-g[a[x]].begin();
				ll t2=lower_bound(g[a[x+1]].begin(),g[a[x+1]].end(),x+1)-g[a[x+1]].begin();
				g[a[x]][t1]++;
				g[a[x+1]][t2]--;
				swap(a[x],a[x+1]);
			}
		}
	}
	return 0;
}
另外,這道題似乎還有主席樹和一堆高階資料結構的方法,不過這次似乎被卡了……一方面有時間得學學這些東西,另一方面以後也不要過度依賴高階資料結構啊qaq

T3 矩陣遊戲

考場

暴力,拿了40分……
其實也考慮過線段樹?但是不會寫……(後來學長說這題帶修改可以用線段樹)

正解

感覺這題寫暴力不應該,畢竟這個柿子窩完全可以推出來啊qaq
以後一定要勇於推柿子啊qaq
所求答案:

\[\begin{aligned} ans&=\sum_{i=1}^{n}\sum_{j=1}^{m} A_{i,j}\cdot R_i\cdot S_j\\ &=\sum_{i=1}^{n}\sum_{j=1}^{m} [m(i-1)+j]\cdot R_i\cdot S_j\\ &=\sum_{i=1}^{n}\sum_{j=1}^{m} m(i-1)\cdot R_i\cdot S_j+\sum_{i=1}^{n}\sum_{j=1}^{m} j\cdot R_i\cdot S_j\\ &=\sum_{j=1}^{m}\sum_{i=1}^{n} m(i-1)\cdot R_i\cdot S_j+\sum_{j=1}^{m}\sum_{i=1}^{n} j\cdot R_i\cdot S_j\\ &=\sum_{j=1}^{m}S_j\sum_{i=1}^{n} m(i-1)\cdot R_i+\sum_{j=1}^{m}j\cdot S_j\sum_{i=1}^{n} R_i\\ \end{aligned}\]

這兩個柿子中,\(\sum_{i=1}^{n} m(i-1)\cdot R_i\)\(\sum_{i=1}^{n} R_i\)這兩部分都只與\(i\)有關,我們可以\(O(n)\)求出
對於\(S_i\)\(R_i\),因為乘法的先後對結果沒有影響,所以直接離線乘起來
然後再對於每個\(j\)更新答案,注意隨時取模(本題資料炸\(long\ long\)非常之容易,如果沒取好會炸成44分,可只比暴力高4分啊qaq
code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define N 1000005
#define mod 1000000007

ll R[N],S[N];
ll n,m,k;
char s[3];
ll x,y;
ll sum1=0;
ll sum2=0;
ll ans=0;

int main()
{
	scanf("%lld%lld%lld",&n,&m,&k);
	for(int i=1;i<=n;i++)R[i]=1;
	for(int j=1;j<=m;j++)S[j]=1;
	for(int i=1;i<=k;i++)
	{
		scanf("%s%lld%lld",s,&x,&y);
		if(s[0]=='R')R[x]=R[x]*y%mod;
		else if(s[0]=='S')S[x]=S[x]*y%mod;
	}
	for(int i=1;i<=n;i++)sum1=(sum1+m*(i-1)%mod*R[i]%mod)%mod;//cout<<sum1<<endl;
	for(int i=1;i<=n;i++)sum2=(sum2+R[i])%mod;//cout<<' '<<sum2<<endl;
	for(int j=1;j<=m;j++)ans=(ans+sum1*S[j]%mod)%mod;//cout<<"  "<<ans<<endl;
	for(int j=1;j<=m;j++)ans=(ans+j*S[j]%mod*sum2%mod)%mod;//cout<<"   "<<ans<<endl;
	printf("%lld",ans);
	return 0;
}

T4 優美序列

考場(非正解)

可以發現詢問的區間要變成優美區間必須讓他最大最小值中間的數補齊,所以可以開一個線段樹維護原數列的最大最小值
然後可以再開一個數組,記錄\(i\)在原數列中的下標,由剛剛求到的最值可以得到一個區間,這個區間的最大最小值所代表的原陣列中的區間就是補齊了原來缺的數,可以再開一個線段樹來維護這個最值
但是,由於可能有其他的數加入,這個時候得到的區間可能並不是優美的,所以把這個區間再拿去重複處理,直到這段區間\(max-min=\)區間長度
這樣可以拿到78分,由於最後反覆擴充套件,這種方法在這時會效率很低(或許是平方的?
(有學長查區間最值用的是\(ST\)表,似乎能多得4分
78分code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define N 100005
#define ls rt<<1
#define rs rt<<1|1
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r

ll n,m,x,y,ttl,ttr;
ll a[N],b[N];

struct node
{
	ll mx,mi;
}tr[2][N<<2];

void up(ll id,ll rt)
{
	tr[id][rt].mx=max(tr[id][ls].mx,tr[id][rs].mx);
	tr[id][rt].mi=min(tr[id][ls].mi,tr[id][rs].mi);
}

void build1(ll rt,ll l,ll r)
{
	if(l==r)
	{
		tr[0][rt].mi=tr[0][rt].mx=a[l];
		//cout<<tr[0][rt].mi<<' ';
		return;
	}
	ll m=(l+r)>>1;
	build1(lson);
	build1(rson);
	up(0,rt);
}

void build2(ll rt,ll l,ll r)
{
	if(l==r)
	{
		tr[1][rt].mi=tr[1][rt].mx=b[l];
		//cout<<tr[1][rt].mx<<' ';
		return;
	}
	ll m=(l+r)>>1;
	build2(lson);
	build2(rson);
	up(1,rt);
}

ll querymax(ll id,ll rt,ll l,ll r,ll nl,ll nr)
{
	if(nl<=l&&r<=nr)
	{
		return tr[id][rt].mx;
	}
	ll m=(l+r)>>1;
	ll res=0;
	if(m>=nl)res=max(res,querymax(id,lson,nl,nr));
	if(m<nr)res=max(res,querymax(id,rson,nl,nr));
	return res;
}

ll querymin(ll id,ll rt,ll l,ll r,ll nl,ll nr)
{
	if(nl<=l&&r<=nr)
	{
		return tr[id][rt].mi;
	}
	ll m=(l+r)>>1;
	ll res=N;
	if(m>=nl)res=min(res,querymin(id,lson,nl,nr));
	if(m<nr)res=min(res,querymin(id,rson,nl,nr));
	return res;
}

int main()
{
	//freopen("owo.in","r",stdin);
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)b[a[i]]=i;
	build1(1,1,n);//cout<<endl;
	build2(1,1,n);//cout<<endl;
	scanf("%lld",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld",&ttl,&ttr);
		do{
		x=querymin(0,1,1,n,ttl,ttr);
		y=querymax(0,1,1,n,ttl,ttr);
		ttl=querymin(1,1,1,n,x,y);
		ttr=querymax(1,1,1,n,x,y);
		}while(ttr-ttl+1!=querymax(0,1,1,n,ttl,ttr)-querymin(0,1,1,n,ttl,ttr)+1);
		printf("%lld %lld\n",ttl,ttr);
	}
	return 0;
}

正解

似乎有很多種,在補了在補了qwq