buctoj2021年ACM竞赛班训练(四)全题解

A

添加链接描述

B-平方斐波那契

题意:给你个数n,回答第n个斐波那契数是不是平方数
斐波那契数定义为f1=1,f2=1,f3=f1+f2…fn=fn-1+fn-2
思路:有一个很明显的性质是只有1和144是平方数,其余都不是平方数

#include<bits/stdc++.h>
using namespace std;
int main()
{
    long long n ;
    cin >> n ;
    if(n == 1 || n == 2 || n == 12) puts("1");
    else puts("0");
    return 0 ;
}

在这里插入图片描述

问题 C: 逃学的小孩

(在CSDN搜这道题的话可能会出现什么树的直径的做法,但是本人太菜了所以就只复述下东子哥的两次遍历吧)

题意解读:
很明显,这道题的结构模型是树,并且2<=n<=200000。第一眼看上去的话,首先应该找三个点,那就遍历所有的节点,找出距离它最远的两点即可……但是,在树形结构上, 计算出离某个节点最远的两个节点需要 O(n) 的复杂度,而我们还需要遍历树中所有的点(并不知道哪一点最优),这样一来,时间复杂度就成了O(n2),抱歉,超时。
东子哥友情赞助
那应该对节点进行怎样的操作呢?找到距离此节点最远的三个最长距离,并且可以保证这三条最远路径必然在当前节点的三个不同的子树中(题目保证任两个居住点间有且仅有一条通路),但是只是单纯枚举所有节点去求其对应的最长距离……好像又会超时,因此这里需要改变一下思路,对树进行两次遍历:

那就随便定一个点作为根节点,然后进行第一次的dfs,算出每个节点的所有子树中所能到达的最远的三个距离;
注意此次遍历忽略了从当前节点的父节点这一分支,因此在这里需要进行第二次dfs,将这条被忽略的路径也添加到所维护的距当前节点最远的三条路径中。

在这之后就是遍历所有节点,寻找最优解的过程了。详细流程看代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=400010; 
int n;
long long m;
int h[N],ne[N],e[N],idx;//运用链表存储边 
long long w[N];
long long ma[N][5];//存储此节点所能到达的三条最远距离 
void add(int a,int b,long long c)
{
 e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
 return ;
}//新建边 
void dfs1(int u,int up)
{
 for(int i=h[u];i!=-1;i=ne[i])
 {
  int v=e[i];
  if(v==up) continue;
  dfs1(v,u);
  if(ma[v][0]+w[i]>ma[u][2])
  {
   ma[u][2]=ma[v][0]+w[i];
   sort(ma[u],ma[u]+3,greater<long long>());
  }
 }
 return ;
}//第一次循环,从根节点开始深搜, 算出子树到当前节点的三条最远距离 
void dfs2(int u,int up,long long mag)
{
    if(ma[u][2]<mag)
    {
        ma[u][2]=mag;
        sort(ma[u],ma[u]+3,greater<long long>());
    }//将从父节点那边到达的最远距离加入到三条路径中 
 for(int i=h[u];i!=-1;i=ne[i])
 {
  int v=e[i];
  if(v==up) continue;
  
   if(ma[u][0]==ma[v][0]+w[i]) dfs2(v,u,ma[u][1]+w[i]);
   else dfs2(v,u,ma[u][0]+w[i]);
   //这里把父节点所能到达的最远距离传到子节点那里时,
  //要判断这个最远距离是否原本就是从这个子节点传上来的 
 }
 return ;
}
int main()
{
 scanf("%d %lld",&n,&m);
 memset(h,-1,sizeof h);
 memset(ma,0,sizeof ma);
 
 int a,b;
 long long c;
 while(m--)
 {
  scanf("%d %d %lld",&a,&b,&c);
  add(a,b,c);
  add(b,a,c);
 }
 
 dfs1(1,0);
 dfs2(1,0,0);
 
 long long res=0;
 for(int i=1;i<=n;i++)
    res=max(res,ma[i][0]+2*ma[i][1]+ma[i][2]); 
    //遍历所有节点,求最优解 
    /**
    这里东子哥原本的思路是特判当前节点有(1、2、>=3)条边,
    然后我初始化ma数组均为零,勉强糊弄过去了……
    **/
 
    printf("%lld",res);
    
    // for(int i=1;i<=n;i++)
    // printf("%lld %lld %lld\n",ma[i][0],ma[i][1],ma[i][2]);
 return 0;
} 

问题 D: 行列式

题意解读:
解题思路都在线代书里,不用多说了吧……
好像直接搜行列式解法的话会用到逆序数之类的,不过咱也不懂,就把高斯消元的码挂下面了。
详细步骤就是先模拟手动化简行列式为上三角行列式,然后将主对角线上的元素相乘,完事。
注意事项有三:

  1. 不要用double,否则会爆精度 ();
  2. 看到mod,就该想到运用 逆元 ,即将对一个数的除法转换为对这个数的逆元的乘法,因为只需要将高斯消元中的除法全部换掉就行,在这里就不废话了;
  3. 其实不会用逆元也可以用下辗转相除法,这是用来求最大公约数的板子,在这里可以用来处理行列式的两列相消,意外的好用,下面是这种方法的码(人菜码多别骂了)。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N=510;
int n;
long long m;
long long s[N][N]; 
long long ans=1;
void grd(int a,int b,int c)
{
 if(s[b][c]!=0)
 {
  long long t=s[a][c]/s[b][c];
  s[a][c]%=s[b][c];
  
  for(int i=c+1;i<n;i++)
  s[a][i]=(s[a][i]-t*s[b][i])%m;
  
  grd(b,a,c);
 }
 return ;
}
long long gauss()
{
    int c,r;// c 代表 列 col , r 代表 行 row
    for(r=0,c=0;c<n;c++)
    {
        int t=r;
        for(int i=r;i<n;i++)
        if(llabs(s[i][c])>llabs(s[t][c])) 
            t=i;
        //找当前列的最大值 
        if(llabs(s[t][c])==0) break;
        //如果最大是0的话,说明次行列式的秩 < n ,行列式值必然是零 
        if(t!=r)
        {
         for(int i=c;i<n;i++) swap(s[t][i],s[r][i]);
         
         ans*=-1;
  }
         
        if(s[r][c]<0) 
        {
         for(int i=c;i<n;i++) s[r][i]*=-1;
         
         ans*=-1;
  }
  
        for(int i=r+1;i<n;i++) 
        {
         
   if(s[i][c]<0) 
            {
         for(int j=c;j<n;j++) s[i][j]*=-1;
         
         ans*=-1;
      }
         if(s[i][c]==0) continue; 
         grd(r,i,c);
         
         if(s[r][c]==0)
         {
          for(int j=c;j<n;j++) swap(s[r][j],s[i][j]);
          
          ans*=-1;
   }
         
  }           
        r++;
    }
    if(r<n) return 0;
     
    for(int i=0;i<n;i++)
    ans=(long long)(ans*s[i][i]%m+m)%m;
     
    return (ans%m+m)%m; 
} 
int main()
{
    scanf("%d %lld",&n,&m);
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
    scanf("%lld",&s[i][j]);
     
    long long t=gauss();
    if(t==0)
    printf("0");
    else printf("%lld",t);
     
    return 0;
}

E: 二叉树的宽度

题意:我们定义:二叉树每一层中,节点最多的那层的节点数为二叉树的宽度。 每行输入一棵二叉树的带虚结点(#)表示的前序遍历序串,长度不超过2000。每个结点为一个小写字母或一个数字。请编写一个程序计算二叉树的宽度。
思路:首先来看看二叉树的图
在这里插入图片描述
1 可以看到第一层的第一个节点是1(从2的0次方-2的1次方-1)
第二层的节点是2到3 (从2的1次方-2的2次方-1)
第三层的节点是4到7 (从2的2次方-2的3次方-1)
第四层的节点是8到15 (从2的3次方-2的4次方-1)
第n层的节点是从2的n-1次方到2的n次方-1
2 除次之外 对于每一个节点
它的左儿子是它的2倍
它的右儿子是它的2倍+1
3 树有三种遍历方式 前/中/后序遍历
前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
若二叉树为空则结束返回,否则:
(1)访问根结点。
(2)前序遍历左子树。
(3)前序遍历右子树 。
需要注意的是:遍历左右子树时仍然采用前序遍历方法。
如所示二叉树
在这里插入图片描述

前序遍历结果:ABDECF
所以给定一个前序遍历+带虚节点
可以用数组保存数的方式+递归建立这棵树
由于在递归的过程中,节点以指数增长
可能会爆空间,所以数组要改成map进行离散化,防止空间复杂度太高
然后建立这颗树之后,对每一层统计节点个数
在更新最大值

#include<bits/stdc++.h>
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define re register int
typedef long long ll ;
using namespace std;
const int N = 2010 , M = 1e6 + 10 ;
char s[M] ;
int a[M] ;
int k = 1 ;
unordered_map<int,int> x ;
int n ;
void dfs(int u)
{
    if(s[k] == '#') return ;
    if(s[k] != '#')
    {   
        x[u] = 1 ;
        k ++ ;
        dfs(u << 1) ;
        k ++ ;
        dfs(u << 1 | 1);
    }
}
int main()
{
    for(int i = 0 ; i <= 20 ; i ++) a[i] = 1 << i ;
    while(cin >> s + 1 )
    {
        n = strlen(s + 1) ;
        k = 1 ;
        x.clear();
        dfs(1) ;
        int ans = 0 ;
        int m = 15 ;
        while(m --)
        {
            int res = 0 ;
            for(int i = a[m-1] ; i <= a[m] - 1 && i <= 4 * n; i ++)
            {
                if(x[i]) res ++ ;
            }
            ans = max(ans,res) ;
        }
        
        cout << ans << endl;
    }
}

二叉树的其他遍历方式详见
添加链接描述

F-合法入栈顺序

题意:一个入栈序列是{1,2,3}的合法出栈序列有{3,2,1},{1,2,3}等,而{3,1,2}是不合法的.现在有一个长度为n的序列A(保证序列内数字唯一,且1<=A[i]<=n)。他想知道这个序列是不是入栈顺序{1,2,3,…n}的合法出栈序列,如果是输出YES,否则输出NO
思路:首先,要知道栈stack是一种数据结构,插入和删除只在表的一端进行。这一端称为栈顶(Stack Top),另一端则为栈底(Stack Bottom)。堆栈的元素插入称为入栈,元素的删除称为出栈。
图解为
在这里插入图片描述

面对任何一个状态, 我们只有两种选择:
把下一个数进栈
把当前栈顶的数出栈(如果栈非空)
要判断当前的序列是否为合法序列
贪心的角度来想,序列的第一个数一定是第一个弹出去的,第二个也是第二个弹出去的,第n个数是第n个弹出来的.所以将1到n在入栈的时候,每插入一个数,就判断当前栈顶元素是不是序列的第一个数,如果是就弹出栈顶,然后继续判断栈顶元素是不是序列的第二个数,如果不是就继续插入,然后一直重复此过程.
如果最后栈为空说明弹完了,是合法序列,否则不是.

#include<bits/stdc++.h>
#define fer(i,a,b) for(int i = a ; i <= b ; i ++)
#define re register int
typedef long long ll ;
using namespace std;
const int N = 110000 ;
int t ;
int n ;
int a[N] ;
int main()
{
	cin >> t ;
    while(t--)
    {
    	stack<int> q ;
    	while(q.size()) q.pop() ;
    	cin >> n ;
    	fer(i,1,n) cin >> a[i] ;
    	int k = 1 ;
    	for(int i = 1 ; i <= n ; i ++)
    	{
    		q.push(i);
    		while(q.size() && q.top() == a[k]) 
    		{
    			k ++ ;
    			q.pop() ;
			}
		}
		if(q.size()) puts("NO");
		else puts("YES");
	}
	return 0 ;
}

G

添加链接描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值