河南萌新联赛2025第(五)场:信息工程大学”(补题)

文章目录

  • 前言
  • [A. 宇宙终极能量调和与多维时空稳定性验证下的基础算术可行性研究](#A. 宇宙终极能量调和与多维时空稳定性验证下的基础算术可行性研究)
  • [B. 中位数](#B. 中位数)
  • [C. 中位数+1](#C. 中位数+1)
  • [E. 中位数+3](#E. 中位数+3)
  • [F. 中位数+4](#F. 中位数+4)
  • [G. 简单题](#G. 简单题)
  • [H. 简单题+](#H. 简单题+)
  • [I. 从零开始的近世代数复习(easy)](#I. 从零开始的近世代数复习(easy))
  • [K. 狂飙追击](#K. 狂飙追击)
  • [L. 防k题](#L. 防k题)
  • 总结·

前言

很无语的一场,说到底,还是自己太钻牛角尖。

比赛链接:河南萌新联赛2025第(五)场:信息工程大学"


A. 宇宙终极能量调和与多维时空稳定性验证下的基础算术可行性研究

直接靠猜,从0到2,试过去了。直接输出2就行

B. 中位数

通过手写一个例子,会发现,到最后就是最大与最小和的一半

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
const ll N=1e6+10;
ll a[N];
void solve()
{
	ll n;
	cin>>n;
	for(ll i=1;i<=n;i++)
	cin>>a[i];
	if(n<=2)
	{
		if(n==1)
		cout<<a[1]<<endl;
		else
		cout<<(a[1]+a[2])/2<<endl;
		return ;
	}
	sort(a+1,a+n+1);
	cout<<(a[1]+a[n])/2<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	// cin>>t;
	while(t--)
	solve();
	return 0;
}

C. 中位数+1

通过题目,会发现是个对顶堆的模板,其实这道题比赛的时候看了,知道该怎么操作,但是忘了代码应该如何写了,直接放弃了,还是自己的实现能力不强。

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
const ll N=1e6+10;
void solve()
{
	ll n;
	cin>>n;
	//例子 1,2,3,4,5
	priority_queue<ll,vector<ll>,greater<ll>> mi;//存放从小到大的,堆顶为该堆的最小值{5,4}
	priority_queue<ll,vector<ll>,less<ll>> ma;//存放从大到小的,堆顶为改堆的最大值,存{1,2,3}
	for(ll i=1;i<=n;i++)
	{
		ll a;
		cin>>a;
		if(ma.empty()||a<=ma.top())
		ma.push(a);
		else
		mi.push(a);
		if(ma.size()>mi.size()+1)
		{
			mi.push(ma.top());
			ma.pop();
		}
		else if(mi.size()>ma.size())
		{
			ma.push(mi.top());
			mi.pop();
		}
		if(i%2!=0)
		{
			cout<<ma.top()<<" ";
		}
		else
		{
			cout<<(ma.top()+mi.top())/2<<" ";
		}
	}
	
}
signed main()
{
	IOS;
	ll t=1;
	// cin>>t;
	while(t--)
	solve();
	return 0;
}

E. 中位数+3

这一题需要用到一个公式Legendre 公式

问题是:在 K 进制下求 n ! n! n!(n 的阶乘 )的后置零的个数。

然而后置零取决于该阶乘中包含多少个k的因子。

例如:

在十进制下,后置零的个数由 10 的因子个数决定,而 10 = 2 × 5 10 = 2 \times 5 10=2×5,所以实际上由 2 和 5 中较少的那个因子个数决定。

比如:如果 K = 12 K = 12 K=12,则分解为 2 2 × 3 1 2^2 \times 3^1 22×31。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
const ll N=1e6+10;
ll n,k;
vector<pii> p;
void yin()
{
    // 从2开始遍历到sqrt(k),寻找k的素因数
    for(ll i=2;i*i<=k;i++)
    {
        // 如果i是k的因数
        if(k%i==0)
        {
            ll cnt=0;
            // 统计i作为素因数的指数
            while(k%i==0)
            {
                k/=i;
                cnt++;
            }
            // 将素因数i和其指数cnt存入vector p中
            p.push_back({i,cnt});
        }
    }
    // 如果k最后大于1,说明剩下的k本身是一个素数
    if(k>1)
        p.push_back({k,1});
}
void solve()
{
    cin>>n>>k;
    // 对k进行素因数分解
    yin();
    // 初始化答案为一个很大的数,用于后续取最小值
    ll ans=1e18;
    // 遍历k的所有素因数及其指数
    for(auto i:p)
    {
        // a是当前素因数
        ll a=i.fi;
        // s是当前素因数在k中的指数
        ll s=i.se;
        // num用于统计n!中包含a的个数
        ll num=0;
        ll c=n;
        // 使用Legendre公式计算n!中a的个数
        while(c)
        {
            num+=c/a;
            c/=a;
        }
        // 计算当前素因数能组成多少个k,取最小值作为可能的答案
        ans=min(ans,num/s);
    }
    // 输出最终结果,即k进制下n!的后置零个数
    cout<<ans<<endl;
}
signed main()
{
    IOS;
    ll t=1;
    // cin>>t;
    while(t--)
        solve();
    return 0;
}

F. 中位数+4

同样是让求后置零,但是在10进制下,只需要循环判断就行了

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
const ll N=1e6+10;
void solve()
{
	ll n,k;
	cin>>n>>k;
	ll ans=0;
	while(n%k==0)
	{
		n=n/k;
		ans++;
	}
	cout<<ans<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	// cin>>t;
	while(t--)
	solve();
	return 0;
}

G. 简单题

看到这一题,以为是数相加,然后找规律,结果啊,wa16次,虽然其中有过怀疑是行列式,二阶还好,还会,到了三阶行列,不会了,想着这才大一,怎么会出这方面的知识,然后直接舍弃掉这方面的想法。继续埋头计算和的规律,呵呵,到头来小丑一个。

说到底还是数学没学好,记得大一的时候老师当时讲过三阶行列式怎么算的,但是,给忘了。

通过拆分会发现是斐波那契数列,然后找规律就行

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
const ll N=1e6+10;
void solve()
{
	ll n;
	cin>>n;
	if(n%3==1)
        cout<<1<<endl;
    else
        cout<<0<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	// cin>>t;
	while(t--)
	solve();
	return 0;
}

H. 简单题+

这一题是上一题的延伸,只不过求模的数变的很大

需要用到斐波那契数列的求和规律

F(n)=f(n+2)-1;

然后还要利用到矩阵加速,这避免不了矩阵方面的知识


代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
#define vc vector<vector<ll>>
const ll N=1e6+10;
const ll mod=998244353;

// 斐波那契递推矩阵:[[1,1],[1,0]],用于矩阵乘法
vc a={{1,1},{1,0}};
vc jz(vc &b, vc &c)
{
    // 初始化结果矩阵(2x2),初始值为0
    vc ans(2, vector<ll>(2, 0)); 
    // 矩阵乘法核心:i行×k列 乘以 k行×j列 → i行×j列
    for(ll i=0;i<2;i++)          // 结果矩阵的行
        for(ll j=0;j<2;j++)      // 结果矩阵的列
            for(ll k=0;k<2;k++)  // 中间维度(矩阵b的列,矩阵c的行)
                ans[i][j] = (ans[i][j] + b[i][k] * c[k][j] % mod) % mod;
    return ans;
}
vc f(ll p)
{
    // 初始化单位矩阵(乘法单位元,类似数字1)
    vc ans={{1,0},{0,1}};  // 单位矩阵:任何矩阵乘以它都等于自身
    while(p > 0)           // 快速幂核心:分解指数p为二进制
    {
        if(p & 1)          // 如果当前二进制位为1,将结果乘以当前矩阵a
            ans = jz(ans, a);
        p >>= 1;           // 指数右移一位(相当于除以2)
        a = jz(a, a);      // 矩阵a自乘,即a^2, a^4, a^8...(对应二进制位的权重)
    }
    return ans;
}
void solve()
{
    ll n;
    cin >> n;  
    
    // 计算矩阵a的(n+1)次幂,通过矩阵快速幂得到斐波那契相关结果
    vc ans = f(n + 1);
    
    // 结果为矩阵[1][1]位置的值减1,取模后输出
    // 原理:斐波那契数列求和公式与矩阵幂的关系,F(0)+F(1)+...+F(n) = F(n+2) - 1
    cout << (ans[1][1] - 1 + mod) % mod << endl;  // +mod避免负数取模
}

int main()
{
    IOS;  
    ll t = 1; 
    // cin >> t; 
    while(t--)
        solve();  
    return 0;
}

I. 从零开始的近世代数复习(easy)

由于是简单版本,k为2,由此问题退化到了最近公共祖先的路径和

并且注意这个条件:

说明根节点1,是父节点,即是前置定理,所以求出lca不是根节点的话,还要再与根节点求一次lca。当然如果用朴素法的话,肯定会超时,为此需要倍增加速

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
const ll N=1e6+10;
ll g[N][30];
ll f[N][30];
ll d[N];
vector<ll> ed[N];
ll ans=0;
void dfs(ll x,ll fa)//打ST表
{
	f[x][0]=fa;
	d[x]=d[fa]+1;
	for(ll i=1;i<30;i++)
	{
		f[x][i]=f[f[x][i-1]][i-1];
		g[x][i]=g[f[x][i-1]][i-1]+g[x][i-1];
	}
	for(auto i:ed[x])
	if(i!=fa)
	dfs(i,x);
}
ll lca(ll x,ll y)//求lca
{
	if(d[x]<d[y])swap(x,y);
	for(ll i=29;i>=0;i--)
	{
		if(d[f[x][i]]>=d[y])
		{
			ans+=g[x][i];
			x=f[x][i];
		}
	}
	if(x==y)
	return x;
	for(ll i=29;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		{
			//ans+=g[x][i]+g[y][i];
			x=f[x][i];
			y=f[y][i];
		}
	}
	ans+=g[x][0]+g[y][0];
	return f[x][0];
}
void solve()
{
	ll n;
	cin>>n;
	for(ll i=1;i<=n;i++)
	cin>>g[i][0];
	for(ll i=1;i<=n-1;i++)
	{
		ll u,v;
		cin>>u>>v;
		ed[u].push_back(v);
	}
	dfs(1,0);
	ll q;
	cin>>q;
	while(q--)
	{
		ll k;
		cin>>k;
		ll x,y;
		cin>>x>>y;
		ans=g[1][0];
		ll s=lca(x,y);
		if(s!=1)//如果不是根节点,要接着找
		lca(s,1);
		cout<<ans<<endl;
	}
}
signed main()
{
	IOS;
	ll t=1;
	 //cin>>t;
	while(t--)
	solve();
	return 0;
}

K. 狂飙追击

这一题可以用BFS过,当然这是因为数据不够精细,正解是逆序模拟,由于正序有太多的选择了,

然而逆序的优势在于:

每一步的前序状态是唯一的,可以确定性地倒推。

例如:若当前点是 (a, b) 且 a > b,那么它只能由 (a - b, b) 移动而来(因为正向移动时只有 max(x,y)=b 才能让 x 增加 b 到达 a)。

若 tx >= ty(通过交换保证此条件统一处理)

此时 tx 是较大值,根据正向移动规则,tx 只能是由 ty 扩展而来:

若 tx > 2ty:说明最近多次移动都是沿 x 轴(每次增加 ty),可以一次性倒推多步(tx = tx / 2,前提是 tx 为偶数,否则无法整除,无解)。
若 tx ≤ 2
ty:只能倒推一步(tx = tx - ty),因为再往前倒推会导致 tx < ty,破坏当前大小关系。

代码如下:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
const ll N=1e6+10;
void solve()
{
	ll a,b,c,d;
	cin>>a>>b>>c>>d;
	ll ans=0;
	while(1)
	{
		if(a>c||b>d)//判断是否无解
		{
			cout<<-1<<endl;
			return ;
		}
        if(a==c&&b==d)//满足条件直接输出
        {
            cout<<ans<<endl;
            return ;
        }
		if(c<d)//保持终点x最大,这样减少判断情况
		swap(c,d),swap(a,b);
		if(c>=2*d)//只有这种情况下,x除以2,会使步数最小化
		{
			if(c%2!=0)//如果为奇数,则一定不成立,因为最后一步都是由小的传过来,咋样看都是二倍关系
			{
				cout<<-1<<endl;
				return ;
			}
			c/=2;
		}
		else c-=d;//如果小于的话,则说明,是加上y的
		ans++;
	}
}
signed main()
{
	IOS;
	ll t=1;
	// cin>>t;
	while(t--)
	solve();
	return 0;
}

L. 防k题

看完题后,很快就会想到二分,但是唯一比较难搞的是check函数的模拟。

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define pii pair<ll,ll>
#define fi first
#define se second
const ll N=1e6+10;
ll x1,c,z,x2,y2;
bool check(ll x)
{
	ll cc=x1;//咔咔的血量
	ll g=c;//初始攻击
	ll sum=x2;//战士的血量
	while(1)
	{
	//注意一定要小于等于,因为下面的循环模拟,可能会使x<0.
		if(x<=0)//如果咔咔没了,就说明不成立
		return false;
        if(sum<=0)//如果战士死亡,说明成立
		return true;
		for(ll i=1;i<=3;i++)//模拟三次最优攻击
		{
			cc-=y2;//每次咔咔承受一次攻击,并扣除血量
			if(cc<=0)//如果一只咔咔阵亡,则战士换另一只攻击
			x--,cc=x1;//总咔咔数量减1
		}
		sum-=g*x;//战士承受的伤害等于存活咔咔的总攻击
		g+=z;//每回合咔咔的攻击力增加z
	}
}
void solve()
{
	cin>>x1>>c>>z>>x2>>y2;
	ll l=1,r=1e10;
	while(l<r)
	{
		ll mid=l+((r-l)>>1);
		if(check(mid))
		{
			r=mid;
		}
		else 
		l=mid+1;
	}
	cout<<r<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	// cin>>t;
	while(t--)
	solve();
	return 0;
}

总结·

这场比赛,突出了一个问题,就是自己太容易陷入一个方向出不来,还有就是模板太容易忘记了,之前接雨水那一道题的模板也是忘了,差点没写出来,而这次是真的没写出来~~~~~,还有就是比较怕搜索题,一般看到那种题,就没写的欲望,就想着其他方法,这回依旧想其他方法,一如既往,没想出来。看到前面写的,原来自己这么多短板,尽力改掉吧