zoukankan      html  css  js  c++  java
  • 集训模拟赛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

    数据范围

    (1le nle 100000,0le mle 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
    ",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 o 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\%) 的数据,满足(1le Nle 10)(2le Mle 5)

    对于 (100\%) 的数据,满足(1le Nle 1000,2le Mle 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] imes E[i-1])(若(2<ile 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) imes 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
    ",ans);
    	}
    	return 0;
    }
    
    

    NO.4 子串

    题目描述

    有两个仅包含小写英文字母的字符串 (A)(B)

    现在要从字符串 (A) 中取出 (k) 个互不重叠的非空子串,然后把这 (k)个子串按照其在字符串 (A)中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 (B) 相等?

    注意:子串取出的位置不同也认为是不同的方案。

    输入格式

    第一行是三个正整数 (n,m,k),分别表示字符串 (A) 的长度,字符串 (B) 的长度,以及问题描述中所提到的 (k),每两个整数之间用一个空格隔开。

    第二行包含一个长度为 (n) 的字符串,表示字符串 (A)

    第三行包含一个长度为 (m) 的字符串,表示字符串 (B)

    输出格式

    一个整数,表示所求方案数。

    由于答案可能很大,所以这里要求输出答案对 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) 组数据:(1le nle 500,1le mle 50,k=1);
    对于第 (2) 组至第 (3) 组数据:(1le nle 500,1le mle 50,k=2);
    对于第 (4) 组至第 (5) 组数据:(1le nle 500,1le mle 50,k=m)
    对于第 (1) 组至第 (7) 组数据:(1le nle 500,1le mle 50,1le kle m);
    对于第 (1) 组至第 (9) 组数据:(1le nle 1000,1le mle 100,1le kle m)
    对于所有 (10) 组数据:(1le nle 1000,1le mle 200,1le kle m)

    分析

    虎哥的最爱:字符串。
    这个题应该是一个(dp),机房里的大佬们都用的三维或者四维,我在改题的时候看见了一个二维的,效率挺高的,所以分享一下。我们定义(f[i][j][k])为第二个字符串前(j)个字符,第一个字符串里前(i)分出来(k)段的最大值,因为(i)一定是从上一个状态转移来,所以这里可以压一维,然后就是两维,第一维被压掉了。

    我们再使用一个数组(sum[j][k])来进行记录,如果上一位的(s1)(s2)一样,那么(sum)每次都可以在转移的时候进行初始化(也算是压行吧):

    [sum[j][k] = s1[i-1] == s2[j-1] ? sum[j-1][k]+f[j-1][k-1] : 0 ]

    根据这个三维运算符就可以初始化出来,然后就是转移了,看代码

    代码

    
    
    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int maxn = 1005;
    ll f[maxn][maxn];
    ll sum[maxn][maxn],n,m,kl;
    char s1[maxn],s2[maxn];
    int main(){
    	f[0][0]=1;//初始化
    	cin>>n>>m>>kl;
    	cin>>s1>>s2;
    	for(int i=1;i<=n;++i){
    		for(int j=m;j>=1;--j){
    			for(int k=kl;k>=1;k--){
    				f[j][k] = (f[j][k] + (sum[j][k] = s1[i-1] == s2[j-1] ? sum[j-1][k]+f[j-1][k-1] : 0))%1000000007;//加的时候取模,sum在这里处理,从s2长度-1,段数-1转移而来
    			}
    		}
    	}
    	cout<<f[m][kl]<<endl;//分成kl段,长度为m的情况
    	return 0;
    }
    
    
  • 相关阅读:
    模板集合
    [NOIP2005普及组]循环(高精度+数学)
    KEYENCE Programming Contest 2021
    AtCoder Regular Contest 111
    Educational Codeforces Round 99 (Rated for Div. 2)
    AtCoder Beginner Contest 183翻车记
    上古退役选手康复训练1——CSP2020J-2
    [SNOI2020]取石子(数学+打表找规律)
    selenium爬取拉勾网招聘信息
    scrapy将爬取的数据存入MySQL数据库
  • 原文地址:https://www.cnblogs.com/Vocanda/p/13251890.html
Copyright © 2011-2022 走看看