2020-5-9模擬賽題解

前言

6:306:30 開考,我大概 8:008:00 把這套題拍好,充分吸取 NOI Online\texttt{NOI Online} 的教訓,所以每題都拍上了。

正文

T1

題目描述

在一個網格圖中,每次可以從 (x,y)(x,y)

  • 向上移動到 (x1,y)(x-1,y)
  • 向下移動到 (x+1,y)(x+1,y)
  • 向左移動到 (x,y1)(x,y-1)
  • 向右移動到 (x,y+1)(x,y+1)

求從 0,00,0 點出發,依此經過 (x1,y1)(xn,yn)(x_1,y_1)\sim (x_n,y_n) 的最短距離

分析

網格圖中的最短距離 == 曼哈頓距離

曼哈頓距離 == 行的絕對值 ++ 列的絕對值

代碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int n,ans,x[100010],y[100010];
int main(){
	read(n);
	for(int i=1;i<=n;i++)read(x[i]);
	for(int i=1;i<=n;i++)read(y[i]);
	for(int i=1;i<=n;i++){
		ans+=abs(x[i]-x[i-1])+abs(y[i]-y[i-1]);
	}cout<<ans;
	return 0;
}

T2

題目描述

給出 nn 個人的名字和他們的 Rating\texttt{Rating},求這 nn 個人的排名(第 ii 個人的排名定義爲 Rating\texttt{Rating} 比第 ii 個人高的人數 +1+1

分析

這題的答案跟名字沒有關係,而且數據範圍很小。

這樣的話,我們怎麼做都可以。

我的話是對這個數組進行排序,然後暴力查找這個最早出現在第幾個(因爲排名 == Rating\texttt{Rating} 比他高的人數 ++ 11

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int n,a[100010],b[100010];
string st[100010];
bool cmp(int a,int b){
	return a>b;
}
int main(){
	read(n);
	for(int i=1;i<=n;i++)cin>>st[i];
	for(int i=1;i<=n;i++)read(a[i]),b[i]=a[i];
	sort(b+1,b+n+1,cmp);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)//暴力查找
			if(b[j]==a[i]){
				a[i]=j;
				break;
			}
	for(int i=1;i<=n;i++)cout<<a[i]<<" ";
	return 0;
}

T3

題目描述

44 個人 A,B,C,D\texttt{A,B,C,D},每個人有一個實力值。分別爲 a,b,c,da,b,c,d

你現在要把他們分成兩個隊伍,要求每個隊伍裏都得有人,並且使兩隊實力值之和的差最小。

分析

這題直接暴力找出所有方法就好了。

並沒有什麼技巧。

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int main(){
	int a,b,c,d;
	read(a);read(b);read(c);read(d);
	int ans=INT_MAX;
	ans=min(ans,abs((a)-(b+c+d)));
	ans=min(ans,abs((b)-(a+c+d)));
	ans=min(ans,abs((c)-(a+b+d)));
	ans=min(ans,abs((d)-(a+b+c)));
	ans=min(ans,abs((a+b)-(c+d)));
	ans=min(ans,abs((a+c)-(b+d)));
	ans=min(ans,abs((a+d)-(b+c)));
	ans=min(ans,abs((b+c)-(a+d)));
	ans=min(ans,abs((b+d)-(a+c)));
	ans=min(ans,abs((c+d)-(a+b)));
	cout<<ans;
	return 0;
}

T4

題目描述

一個序列,你可以用一個單位的時間從頭部或尾部拿走一個數字,並得到這個數的值。你拿走這個數後,其他沒有被拿走的數字就會全部 1-1

分析

這題的話很顯然有一個結論,那就是喫的順序不會影響到答案。

這樣就簡單了,我們求遍和,再把該減的減去就行了。

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int n,x,s;
int main(){
	read(n);
	for(int i=1;i<=n;i++){
		read(x);
		s+=x;
	}
	cout<<s-(n-1)*n/2;
	return 0;
}

T5

題目描述

子序列的定義:序列 a\texttt{a}b\texttt{b} 的子序列,當且僅當從 b\texttt{b} 中刪除若干個元素能得到 a\texttt{a}
R\texttt{R} 有兩個序列 a\texttt{a}b\texttt{b},
要求你找到一個最長的序列c,滿足以下條件中的任何一個:

  1. c是a的子序列但不是b的子序列;

  2. c是b的子序列但不是a的子序列;

因爲出題人不會寫 spj,所以就只要你輸出c的最長長度即可.
如果找不到,就輸出0.

分析

這題其實並不複雜。

  • 如果兩個序列不完全相同,顯然答案 == max{\max\{11 個序列的長度,第 22 個序列的長度 }\}
  • 如果兩個序列完全相同,答案自然是 00

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int a[100010],b[100010];//見到 105 我就害怕
int main(){
	int n,m;
	read(n);
	for(int i=1;i<=n;i++)read(a[i]);
	read(m);
	for(int i=1;i<=m;i++)read(b[i]);
	if(n!=m)cout<<max(n,m);
	else{
		for(int i=1;i<=n;i++)
			if(a[i]!=b[i]){
				cout<<n;
				return 0;
			}
		cout<<0;
	}
	return 0;
}

T6

題目描述

nn 個石頭,第 ii 個石頭的座標爲 aia_i,不保證 aia_i 有序。
你只能往前跳,並且你必須從 00 開始,中途踩到所有的石頭並最後跳到座標爲m的位置。
你有一個能力值 GGGG 不是定值在一次跳躍中不會變化,但在一次跳躍中你每次能跳躍的距離不能大於你的能力值 GG
有時候你可能跳不到石頭上,這時候你就會落到河裏.安全起見,你只能落水不超過 kk 次。
求出爲了使落水不超過 kk 次,你至少需要的能力值。

分析

這個直接二分 GG,看落水次數是否 k\leq k 就行了。

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int a[100010],n,m,k;
bool check(int z){
	int cishu=0;//拼音應該看的懂的吧
	for(int i=2;i<=n;i++){
		int x=a[i]-a[i-1]-1;
		cishu+=x/z;
	}
	return cishu<=k;
}
int main(){
	read(n);read(m);read(k);
	n++;//a[1]=0;
	for(int i=2;i<=n;i++)read(a[i]);
	a[++n]=m;//一點點的小技巧
	sort(a+1,a+n+1);
	int l=-1,r=INT_MAX;
	while(l+1<r){
		int mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid;
	}cout<<r;
	return 0;
}

T7

題目描述

有一個長爲 nn 的序列 a1,a2,,ana_1,a_2,\sim,a_n
s(L,R)=max{a[L],a[L+1],.....,a[R]}min{a[L],a[L+1],...a[R]}(LR)s(L,R) = \max\{a[L],a[L+1],.....,a[R]\}-\min\{a[L],a[L+1],...a[R]\} (L\leq R),即 s(L,R)s(L,R) 爲序列中第 LL 個數到第 RR 個數的最大值和最小值之差。
求出對於所有的滿足 1LRn1\leq L\leq R\leq nL,RL,Rs(L,R)s(L,R) 之和。

分析

RMQ\texttt{RMQ} 萬歲!智商不夠,數據結構來湊。

我的這種做法很不要動腦子,我暫時很沒找到別的做法。

RMQ\texttt{RMQ} 算法

簡單講講 RMQ\texttt{RMQ}

RMQ\texttt{RMQ} 又稱 ST\texttt{ST} 表,可以實現 O(1)O(1) 靜態區間查詢最大或最小值,線段樹的話會多一個 log\log。並且這種算法初始化的時間複雜度也是非常優秀的—— nlog2nn\log_2 n

這個東西如何實現呢?這個東西本質上就是一個倍增

定義 Fi,jF_{i,j} 表示第 ii+2ji\sim i+2^{j} 個數中最小的。

學過倍增的同學,這個遞推式應該很簡單就能推出來。

重點講查找,其實上面的內容可能不足爲奇,但是查找這部分確實有技術含量了。

首先,設一個數爲 2x2^x

對於任意數,一定可以找到 xx 滿足以下條件:

  • 2x2^x \leq 這個數
  • 2x×22^x\times 2 \geq 這個數

沒有理解也沒關係,我們來看這個算法到達是怎麼實現的

在這裏插入圖片描述

這個圖應該還是滿直觀的

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
ll f1[17][100010],f2[17][100010],b[100010],a[100010],n,ans;
void bulid(){
	for(int j=1;j<=16;j++)
		for(int i=1;i<=n;i++)
			f1[j][i]=max(f1[j-1][i],f1[j-1][i+(1<<(j-1))]);
	for(int j=1;j<=16;j++)
		for(int i=1;i<=n;i++)
			f2[j][i]=min(f2[j-1][i],f2[j-1][i+(1<<(j-1))]);
}//RMQ
int Max(int l,int r){
	int len=r-l+1;
	return max(f1[b[len]][l],f1[b[len]][r+1-(1<<b[len])]);
}
int Min(int l,int r){
	int len=r-l+1;
	return min(f2[b[len]][l],f2[b[len]][r+1-(1<<b[len])]);
}
int main(){
	read(n);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)f1[0][i]=a[i];//RMQ初始化
	for(int i=1;i<=n;i++)f2[0][i]=a[i];//RMQ初始化
	bulid();
	for(int i=1;i<=16;i++)b[1<<i]++;
	for(int i=1;i<=n;i++)b[i]+=b[i-1];
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			ans+=Max(i,j)-Min(i,j);
	cout<<ans;
	return 0;
}

T8

題目描述

有一個長爲 nn 的序列 a1ana_1 \sim a_n,保證序列裏的數字都是 0011
z(x)z(x) 爲關於整數 xx 的函數。

  • xx 爲奇數時 z(x)=1z(x) = 1
  • xx 爲偶數時 z(x)=0z(x) = 0

f(L,R)=z(a[L]+a[L+1]+...+a[R])f(L,R) = z(a[L]+a[L+1]+...+a[R])
s(L,R)=zs(L,R) = z(所有滿足 LijRL\leq i\leq j\leq Rf(i,j)f(i,j) 之和)
qq 次詢問,每次給你一個 L,RL,R,要你求出 s(L,R)s(L,R) 的值。

分析

找規律

這道題我們先不要管 mod 2\bmod\ 2

我們先來看看 1n1\sim n 中每個數在所有 1LRn1\leq L\leq R\leq nLRL\sim R 的區間中被計算了多少次。(本來其實是希望用差分序列找通項式的,結果有意外的驚喜)

先來寫個程序

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int n,a[100010];
int main(){
	read(n);
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
			for(int k=i;k<=j;k++)
				a[k]++;
	for(int i=1;i<=n;i++)cout<<a[i]<<" ";
	return 0;
}

我們來試試不同 nn 的值會對計算次數產生什麼影響。

  • nn == 11 時,程序中的 aa 序列爲 1
  • nn == 22 時,程序中的 aa 序列爲 2 2
  • nn == 33 時,程序中的 aa 序列爲 3 4 3
  • nn == 44 時,程序中的 aa 序列爲 4 6 6 4
  • nn == 55 時,程序中的 aa 序列爲 5 8 9 8 5
  • nn == 66 時,程序中的 aa 序列爲 6 10 12 12 10 6
  • nn == 77 時,程序中的 aa 序列爲 7 12 15 16 15 12 7

這個時候我們再來關注一下 mod2\bmod 2 的餘數

  • nn == 11 時,程序中的 aa 序列爲 1
  • nn == 22 時,程序中的 aa 序列爲 0 0
  • nn == 33 時,程序中的 aa 序列爲 1 0 1
  • nn == 44 時,程序中的 aa 序列爲 0 0 0 0
  • nn == 55 時,程序中的 aa 序列爲 1 0 1 0 1
  • nn == 66 時,程序中的 aa 序列爲 0 0 0 0 0 0
  • nn == 77 時,程序中的 aa 序列爲 1 0 1 0 1 0 1

規律已經很明顯了

  • nn 爲偶數的時候,全部都爲 00
  • nn 爲奇數的時候,一個 00 一個 11 間隔開來的。

所以

  • 當詢問區間長度爲偶數時,直接輸出 00
  • 當詢問區間長度爲奇數是,答案爲 aL,aL+2,aL+4aRa_L,a_{L+2},a_{L+4} \ldots a_{R}

前綴和

如何求出 aL,aL+2,aL+4aRa_L,a_{L+2},a_{L+4} \ldots a_{R} 呢?

我們可以用上前綴和

我們這樣求出

for(int i=1;i<=n;i++)s[i]=s[i-2]+a[i];//似乎RE一點點沒關係

這樣就好辦了,我們直接去解決詢問了。

while(T--){
    int l,r;
    read(l);read(r);
    if((r-l+1)%2==0)puts("0");//區間的長度爲偶數
    else{
        int ans=(s[r]-s[l-2])%2;
        printf("%d\n",ans);
    }
}

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
int a[100010],s[100010],n,T;
int main(){
	read(n);read(T);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)s[i]=s[i-2]+a[i];
	while(T--){
		int l,r;
		read(l);read(r);
		if((r-l+1)%2==0)puts("0");//區間的長度爲偶數
		else{
			int ans=(s[r]-s[l-2])%2;
			printf("%d\n",ans);
		}
	}
	return 0;
}

後記

這場比賽也正是檢查的好,只有有這個習慣,才能保證該有的分數能全部拿到。

到目前爲止,還有一點點的遺憾,T8T8 我確乎不會對那個規律進行證明,繼續思考吧!

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