1. 程式人生 > 實用技巧 >集訓模擬賽8 [虎哥出題的一天]

集訓模擬賽8 [虎哥出題的一天]

前言

相信不用我說什麼了,虎哥出題我必糊……下邊是分析

NO.1 食物鏈

題目

如圖所示為某生態系統的食物網示意圖

現在給你n個物種和m條能量流動關係,求其中的食物鏈條數。
物種的名稱為從1到n編號,m條能量流動關係形如
\(a_1\ b_1\)
\(a_2\ b_2\)
\(a_3\ b_3\)
\(……\)
\(a_{m−1}\ b_{m−1}\)
\(a_m\ b_m\)

其中ai bi表示能量從物種ai流向物種bi。

輸入格式

第一行兩個正整數\(n\)\(m\)
接下來\(m\)行每行兩個整數\(a_i,b_i\)表示\(m\)條能量流動關係。
(資料保證輸入資料符號合生物學特點,且不會有重複的能量流動關係出現)

輸出格式

一個整數即食物網中的食物鏈條數。

樣例輸入

10 16
1 2
1 4
1 10
2 3
2 5
4 3
4 5
4 8
6 8
7 6
7 9
8 5
9 8
10 6
10 7
10 9

樣例輸出

9

資料範圍

\(1\le n\le 100000,0\le m\le 200000\)

分析

這個題其實只是一道裸的記憶化暴搜,其實除了建邊我全都寫對了,然而我當時一時nt,直接反向建邊(因為我覺的這樣好做……)然後就\(WA\)了,以後可不能玩這有的沒的了……
我們統計每個點的入度和出度,顯然入度為\(0\)的點是食物鏈的起點,我們就從這個點開始遞迴,統計到有入度沒出度的點就直接讓\(ans++\),因為他沒有子節點,只有自己本身,然後要記得每一次的答案都要用一個數組記錄下來,不然應該會\(TLE\)

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
struct Node{//建邊結構體
	int v,next;
}e[maxn];
int vis[maxn];
int head[maxn],ans;
int n,m;
int tot,du[maxn],rd[maxn],cd[maxn];
void Add(int x,int y){//建邊
	e[++tot].v = y;
	e[tot].next = head[x];
	head[x] = tot;
}
int Dfs(int x){//i遞迴
	if(du[x])return du[x];//當前點被搜過就直接返回值
	int ans = 0;
	if(!cd[x] && rd[x])ans++;//掃到了食物鏈終點直接答案加一
	for(int i=head[x];i;i=e[i].next)ans+=Dfs(e[i].v);//位元組點的值加起來
	return du[x] = ans;//記憶化並返回當前點的值
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		Add(x,y);//建邊並統計出入度
		cd[x]++;
		rd[y]++;
	}
	for(int i=1;i<=n;++i){
		if(rd[i]==0){//入度為0,從起點出發
			ans+=Dfs(i);
		}
	}
	printf("%d\n",ans);
}

NO.2 升降梯上

題目描述

開啟了升降梯的動力之後,探險隊員們進入了升降梯執行的那條豎直的隧道,映入眼簾的是一條直通塔頂的軌道、一輛停在軌道底部的電梯、和電梯內一杆控制電梯升降的巨大手柄。

\(Nescafe\)之塔一共有\(N\)層,升降梯在每層都有一個停靠點。手柄有\(M\)個控制槽,第\(i\)個控制槽旁邊標著一個數\(C_i\),滿足\(C_1<C_2<C_3<……<C_M\)。如果\(C_i>0\),表示手柄扳動到該槽時,電梯將上升\(C_i\)層;如果\(C_i<0\),表示手柄扳動到該槽時,電梯將下降\(-C_i\)層;並且一定存在一個\(C_i=0\),手柄最初就位於此槽中。注意升降梯只能在\(1\to N\)層間移動,因此扳動到使升降梯移動到\(1\)層以下、\(N\)層以上的控制槽是不允許的。

電梯每移動一層,需要花費\(2\)秒鐘時間,而手柄從一個控制槽扳到相鄰的槽,需要花費\(1\)秒鐘時間。探險隊員現在在\(1\)層,並且想盡快到達\(N\)層,他們想知道從\(1\)層到\(N\)層至少需要多長時間?

輸入格式

第一行兩個正整數\(N\)\(M\)

第二行\(M\)個整數\(C_1、C_2……C_M\)

輸出格式

輸出一個整數表示答案,即至少需要多長時間。若不可能到達輸出\(-1\)

樣例

樣例輸入

6 3
-1 0 2

樣例輸出

19

資料範圍與提示

手柄從第二個槽扳到第三個槽(\(0\)扳到\(2\)),用時\(1\)秒,電梯上升到\(3\)層,用時\(4\)秒。

手柄在第三個槽不動,電梯再上升到\(5\)層,用時\(4\)秒。

手柄扳動到第一個槽(\(2\)扳到\(-1\)),用時\(2\)秒,電梯下降到\(4\)層,用時\(2\)秒。

手柄扳動到第三個槽(\(-1\)扳倒\(2\)),用時\(2\)秒,電梯上升到\(6\)層,用時\(4\)秒。

總用時為\((1+4)+4+(2+2)+(2+4)=19\)秒。

對於\(30\%\) 的資料,滿足\(1\le N\le 10\)\(2\le M\le 5\)

對於 \(100\%\) 的資料,滿足\(1\le N\le 1000,2\le M\le 20\)\(-N<C_1<C_2<……<C_M<N\)

分析

又是一個玄學建邊求最短路的題,只是沒看出來……以後要多練練這方面的思維了。主要思路就是從手柄每個位置之間建邊,邊權就是時間也就是標號差的絕對值。然後每一個手柄的位置在不同的層之間也要建邊,邊權就是樓層差乘以2,樓層差是根據這個手柄能走多少來決定的,具體程式碼註釋裡說。
然後建完邊跑最短路,與最短路的題不一樣的是,這次要統計最頂層到達所有手柄位置中最小的路徑,因為最後手柄不一定拉到哪裡,當然是最小的更優。

程式碼



#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5+10;
int head[maxn],dis[25005],vis[25005];
struct Node{
	int v,next,val;
}e[maxn<<1];
int tot;
int a[maxn],ll,jl[1005][50];
void Add(int x,int y,int z){//建邊
	e[++tot].v = y;
	e[tot].next = head[x];
	head[x] = tot;
	e[tot].val = z;
}
priority_queue<pair<int,int> >q;
void Dij(int x){//堆優化迪傑斯特拉求最短路
	memset(dis,0x3f,sizeof(dis));
	dis[x] = 0;
	q.push(make_pair(0,x));
	while(!q.empty()){
		int y = q.top().second;
		q.pop();
		if(vis[y])continue;
		vis[y] = 1;
		for(int i=head[y];i;i=e[i].next){
			int v = e[i].v;
			int w = e[i].val;
			if(dis[v]>dis[y]+w){
				dis[v] = dis[y]+w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;++i){
		cin>>a[i];
	}
	int cnt = 0;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			jl[i][j] = ++cnt;//把所有的點都賦予一個標號
			if(i == 1 && a[j] == 0)ll = cnt;//記錄手柄初始位置
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			for(int k=1;k<=m;++k){//手柄位置不同就建邊
				if(j!=k)
					Add(jl[i][j],jl[i][k],abs(k-j));//手柄之間建邊,邊權是差值絕對值
			}
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(a[j]!=0 && i+a[j]>=1 && i+a[j]<=n){//不是初始位置且拉完拉桿以後沒有跑到外邊
				Add(jl[i][j],jl[i+a[j]][j],abs(a[j]*2));//邊權為走的樓層數乘以2,按鈕位置不變,只改第一維的高度
			}
		}
	}
	Dij(ll);//從手柄初始位置開始最短路
	int ans = 0x3f3f3f3f;
	for(int i=1;i<=m;++i){
		ans = min(ans,dis[jl[n][i]]);//找到頂層最短的路徑
	}
	if(ans == 0x3f3f3f3f){//沒有路徑就輸出-1
		cout<<-1<<endl;
		return 0;
	}
	cout<<ans<<endl;
	return 0;
}

分析解法2

還有一種就是\(dp\)解法,這是\(lc\)大佬講的,利用\(f[i][j]\)記錄到第\(i\)層,手柄位置為\(j\)的時間,然後從手柄位置為\(k\)轉移而來,取\(min\)即可,下邊程式碼

程式碼

自己寫的時候有點壓行,所以湊合著看吧



#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+4,maxk = 25;
#define ll long long
ll f[maxn][maxk],ans = 0x3f3f3f3f3f3f3f3f,jl[maxn],n,m;
int main(){
	memset(f,0x3f,sizeof(f));
	cin>>n>>m;
	for(int i=1;i<=m;++i){
		cin>>jl[i];
		if(jl[i] == 0)f[1][i] = 0;//第一層初始位置時間為0
	}
	for(int js=1;js<=5;++js)//貪心多貪幾遍,保證正確(lc大佬說的)
		for(int i=1;i<=n;++i)//列舉樓層
			for(int j=1;j<=m;++j)//當前手柄位置
				for(int k=1;k<=m;++k){//上一次手柄位置
					int now = i-jl[k];//這次拉手柄前的位置
					if(now>=1 && now<=n)f[i][j] = min(f[i][j],f[now][k]+abs(j-k)+abs(jl[k]*2));//更新答案
				}
	for(int i=1;i<=m;++i)ans = min(ans,f[n][i]);//頂層取最大值
	printf("%lld",ans == 0x3f3f3f3f3f3f3f3f?-1:ans);return 0;//玄學三維運算子自行理解
}

NO.3 Password

題目描述

\(Rivest\)是密碼學專家。近日他正在研究一種數列\(E=\{E[1],E[2],……,E[n]\}\),且\(E[1]=E[2]=p\)\(p\)為一個質數),\(E[i]=E[i-2]\times E[i-1]\)(若\(2<i\le n\))。例如\(\{2,2,4,8,32,256,8192,……\}\)就是\(p=2\)的數列。在此基礎上他又設計了一種加密演算法,該演算法可以通過一個金鑰\(q(q<p)\)將一個正整數\(n\)加密成另外一個正整數\(d\),計算公式為:\(d=E[n]\ mod\ q\)。現在\(Rivest\)想對一組資料進行加密,但他對程式設計不太感興趣,請你幫助他設計一個數據加密程式。

輸入描述

讀入\(m\)\(p\)。其中\(m\)表示資料個數,\(p\)用來生成數列\(E\)。以下有\(m\)行,每行有\(2\)個整數\(n\)\(q\)\(n\)為待加密資料,\(q\)為金鑰。

輸出描述

\(i\)行輸出第\(i\)個加密後的資料。

樣例輸入

2 7
4 5
4 6

樣例輸出

3
1

分析

這個題其實就是比較裸的數論,只不過是考到的東西非常多,所以我們逐一分析,首先數列\(E\)的求出,我們分析一下給的例子,很容易就能得出這是\(p\)的乘方,乘方數就是菲波納契數列,然後因為\(p\)為質數,所以我們可以根據擴充套件尤拉定理得出公式:

\[p^{\varphi(q)}\equiv 1\ modq \]

因為我們要求的是\(p\)的菲波納契數列第\(n\)項次方\(modq\),所以我們需要的只是菲波納契數列第\(n\)項,根據上邊的公式,我們可以把菲波納契數列第\(n\)項次方轉化為\(p^{\varphi(q)\times k + t}\),利用矩陣快速冪求出這個\(t\)只需要在乘的過程中一直取\(\varphi(q)\)的模就行了,最後再加上一個\(\varphi(q)\),這就是乘方,然後利用快速冪求解。

程式碼



#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll m,p;
ll phimod;
struct juzhen{//矩陣結構體
	ll jz[3][3];
	ll n,m;
	juzhen(){
		memset(jz,0,sizeof(jz));
		n=0;
		m=0;
	}
}fib,tmp;
juzhen operator * (juzhen a,juzhen b){//運算子過載
	juzhen ans;
	ans.n = a.n;
	ans.m = b.m;
	for(int i=1;i<=a.n;++i){
		for(int j=1;j<=b.m;++j){
			ans.jz[i][j] = 0;
			for(int k=1;k<=a.m;++k){
				ans.jz[i][j] = (ans.jz[i][j]%phimod+((a.jz[i][k]%phimod)*(b.jz[k][j]%phimod))%phimod)%phimod;
			}
		}
	}
	return ans;
}
void jzksm(ll k){//矩陣快速冪
	while(k){
		if(k&1){
			fib = fib * tmp;
		}
		tmp = tmp * tmp;
		k>>=1;
	}
}
ll getphi(ll n){//求尤拉函式值
	ll ans = n;
	ll m = sqrt(n+0.5);
	for(int i=2;i<=m;++i){
		if(n%i == 0){
			ans = ans/i*(i-1);
			while(n%i == 0)n=n/i;
		}
	}
	if(n>1)ans = ans/n*(n-1);
	return ans;
}
ll ksm(ll a,ll b,ll mod){//普通快速冪
	ll ans = 1;
	while(b){
		if(b&1){
			ans = ans*a%mod;
		}
		a = a*a%mod;
		b>>=1;
	}
	return ans%mod;
}
int main(){
	scanf("%lld%lld",&m,&p);
	for(int i=1;i<=m;++i){//初始化
		fib.n = 1;
		fib.m=2;
		fib.jz[1][1] = fib.jz[1][2] = 1;
		tmp.n = tmp.m = 2;
		tmp.jz[1][1] = tmp.jz[1][2]=tmp.jz[2][1] = 1;
		tmp.jz[2][2] = 0;
		ll n,q;
		scanf("%lld%lld",&n,&q);
		phimod = getphi(q);//求出q的尤拉函式值
		if(n>=3LL)jzksm(n-2);//n比3大才需要求冪
		ll fang = fib.jz[1][1]+phimod;//答案應該是多少次方
		ll ans = ksm(p,fang,q);//快速冪
		printf("%lld\n",ans);
	}
	return 0;
}

NO.4 子串

題目描述

有兩個僅包含小寫英文字母的字串 AAA 和 BBB。

現在要從字串 AAA 中取出 kkk 個互不重疊的非空子串,然後把這 kkk 個子串按照其在字串 AAA 中出現的順序依次連線起來得到一個新的字串。請問有多少種方案可以使得這個新串與字串 BBB 相等?

注意:子串取出的位置不同也認為是不同的方案。
輸入格式

第一行是三個正整數 n,m,kn,m,kn,m,k,分別表示字串 AAA 的長度,字串 BBB 的長度,以及問題描述中所提到的 kkk,每兩個整數之間用一個空格隔開。

第二行包含一個長度為 nnn 的字串,表示字串 AAA。

第三行包含一個長度為 mmm 的字串,表示字串 BBB。
輸出格式

一個整數,表示所求方案數。

由於答案可能很大,所以這裡要求輸出答案對 100000000710000000071000000007 取模的結果。
輸入輸出樣例
輸入 #1

6 3 1
aabaab
aab

輸出 #1

2

輸入 #2

6 3 2
aabaab
aab

輸出 #2

7

輸入 #3

6 3 3
aabaab
aab

輸出 #3

7

說明/提示

對於第 1 組資料:1≤n≤500,1≤m≤50,k=11≤n≤500,1≤m≤50,k=11≤n≤500,1≤m≤50,k=1;
對於第 2 組至第 3 組資料:1≤n≤500,1≤m≤50,k=21≤n≤500,1≤m≤50,k=21≤n≤500,1≤m≤50,k=2;
對於第 4 組至第 5 組資料:1≤n≤500,1≤m≤50,k=m1≤n≤500,1≤m≤50,k=m1≤n≤500,1≤m≤50,k=m;
對於第 1 組至第 7 組資料:1≤n≤500,1≤m≤50,1≤k≤m1≤n≤500,1≤m≤50,1≤k≤m1≤n≤500,1≤m≤50,1≤k≤m;
對於第 1 組至第 9 組資料:1≤n≤1000,1≤m≤100,1≤k≤m1≤n≤1000,1≤m≤100,1≤k≤m1≤n≤1000,1≤m≤100,1≤k≤m;
對於所有 10 組資料:1≤n≤1000,1≤m≤200,1≤k≤m1≤n≤1000,1≤m≤200,1≤k≤m1≤n≤1000,1≤m≤200,1≤k≤m。