河南萌新联赛2025第(三)场:河南理工大学(补题)

前言

这次比赛只能说收获不少。


A.检讨

题目传送门:检讨
在这里插入图片描述
在这里插入图片描述
思路:就是二分+前缀和,当然,这个二分也不是太好想,
二分推理:
重要条件:
在这里插入图片描述
推导过程:
子数组 [i…j] 的平均值 ≥ m →
∑ k = i j a k j − i + 1 ≥ m \frac{\sum_{k=i}^j a_k}{j-i+1} \geq m ji+1k=ijakm
两边同乘 (j-i+1)(正数,不改变不等号方向)→
∑ k = i j a k ≥ m ⋅ ( j − i + 1 ) \sum_{k=i}^j a_k \geq m \cdot (j-i+1) k=ijakm(ji+1)
变形为:
∑ k = i j ( a k − m ) ≥ 0 \sum_{k=i}^j (a_k - m) \geq 0 k=ij(akm)0
如果我们构造前缀和数组 sum,其中 sum[i] = (a₁ - m) + (a₂ - m) + … + (aᵢ - m),那么子数组 [i…j] 满足条件等价于:
s u m [ j ] − s u m [ i − 1 ] ≥ 0 sum[j] - sum[i-1] \geq 0 sum[j]sum[i1]0
为了找到长度 ≥ L 的子数组,我们需要:
1. 遍历到 j 时,在 [1…j-L] 范围内找最小的 sum[i-1](记为 min_sum)。
2. 如果 sum[j] ≥ min_sum,说明存在子数组 [i…j](长度 ≥ L)满足条件,返回 true。
这个前缀和,只能说非常的妙,通过

if (i >= L) {
            a = min(a, sum[i - L]); 
        }

这一步取最小值将a锁定到了
「sum[0], sum[1], …, sum[i-L] 中的最小值」,确保我们能最容易地找到满足 sum[i] ≥ a 的情况
也就是:

if(sum[i]>=a)
		return true;

至于为什么是取最小值:
假设当前遍历到位置 j(即 i = j),此时需要考虑所有可能的起点 i,使得子数组 [i…j] 的长度 ≥ L(即 i ≤ j - L + 1)。
对应的前缀和条件是:
sum[j] - sum[i-1] ≥ 0 → sum[j] ≥ sum[i-1]。

要让这个不等式成立,最容易满足的情况是 sum[i-1] 尽可能小。
因此,我们只需要记录「i-1 在 [0, j-L] 范围内的最小 sum 值」(即 sum[i-L],因为 i-1 = j-L 对应 i = j-L+1),然后判断 sum[j] 是否 ≥ 这个最小值即可。

同时这个前缀和很好的优化了时间复杂度,如果仅是判断>=L
1e6的时间复杂度通过嵌套循环将会超时;
如下:

double bruteForce() {
    double maxAvg = -1e18;
    // 枚举所有可能的起点
    for (int i = 1; i <= n; i++) {
        ll sum = 0;
        // 枚举所有可能的终点(保证长度 ≥ L)
        for (int j = i; j <= n; j++) {
            sum += s[j];
            int len = j - i + 1;
            if (len >= L) {  // 直接比较区间长度
                double avg = (double)sum / len;
                maxAvg = max(maxAvg, avg);
            }
        }
    }
    return maxAvg;
}

而前缀和:
当i >= L时,才开始记录前缀和的最小值(a = min(a, sum[i-L]))
此时i-L对应的起点,与当前i形成的区间长度恰好为L(i - (i-L+1) + 1 = L)
随着i增大,i-L也增大,自然包含了更长的区间(长度L+1、L+2等)
如下也就是核心代码:

bool check(double m)
{
	double a=1e10;
	for(ll i=1;i<=n;i++)
	{
		sum[i]=sum[i-1]+s[i]-m;//通过上面的推导得到
		 if(i>=L)
		 a=min(a,sum[i-L]);//相当于区间长度尽量大于等于L
		if(sum[i]>=a)
		return true;
	}
	return false;
}

完整代码:

#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;
const ll INF=1e18;
ll s[N];
double sum[N];
ll n,L;
bool check(double m)//核心判断条件
{
	double a=1e10;
	for(ll i=1;i<=n;i++)
	{
		sum[i]=sum[i-1]+s[i]-m;
		 if(i>=L)
		 a=min(a,sum[i-L]);
		if(sum[i]>=a)
		return true;
	}
	return false;
}
void slove()
{
	cin>>n>>L;
	for(ll i=1;i<=n;i++)
	{
		cin>>s[i];
	}
	double l=-1e8,r=1e8;
	double ans=0;
	while((r-l)>0.00001)//二分
	{
		double mid=(l+r)/2;
		if(check(mid))
		{
			l=mid;
			ans=mid;
		}
		else
		r=mid;
	}
	printf("%.8lf\n",ans);
}
signed main()
{
	IOS;
	ll t=1;
	cin>>t;
	while(t--)
	slove();
	return 0;
}

B.上海保卫战

题目传送门:上海保卫战
在这里插入图片描述
在这里插入图片描述
思路:其实题目只是一个幌子,通过观察,你就会发现,只需要,对其进行奇偶判断就行,如果是奇数,就减去最小质除数
因为奇-奇=偶,回到偶数情况。当然从这也学到了一个知识点
就是判断最小质除数:

ll get(ll x)
{
	for(ll i=2;i*i<=x;i++)
	if(x%i==0)
	return i;
	return x;
}

1. 质除数(质因数)的定义
如果一个数 i 满足:

i 是质数(只能被 1 和自身整除)
i 能整除 n(即 n % i == 0)

那么 i 就是 n 的质除数(质因数)。

2. 从小到大枚举的逻辑
由于是从小到大遍历,第一个满足 n % i == 0 的 i,必然是 n 的最小质除数,原因有两点:

i 是质数:如果 i 是合数,那它一定可以分解为更小的质数乘积(比如 i = 4 可分解为 2×2 )。但我们是从小到大枚举的,若 i 是合数,那它的质因数会比 i 更小,且已经被枚举过了。因此,能通过 n % i == 0 找到的第一个 i,一定是质数。
i 是最小的:因为是从 2 开始往上找,第一个能整除 n 的质数,自然就是 n 的最小质除数。
完整代码:

#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;
const ll INF=1e18;
ll get(ll x)//找到最小质除数
{
	for(ll i=2;i*i<=x;i++)
	if(x%i==0)
	return i;
	return x;
}
void slove()
{
	ll n;
	cin>>n;
	ll ans=1;
	ll m=0;
	if(n&1)//奇数情况
	{
		 m=get(n);
		ans+=(n-m)/2+1;//变成偶数进行处理
	}
	else//偶数
	 ans+=n/2;
	cout<<ans<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	//cin>>t;
	while(t--)
	slove();
	return 0;
}

E.星际争霸

题目传送门:星际争霸
在这里插入图片描述
在这里插入图片描述
思路:就是前缀和的应用,这一题,本人想的比较复杂,就是先前缀和,然后排序,通过比较追猎者的攻击力与要塞的防御力来进行最大化,其实最开始,把这个题目与之前老鼠找洞的题,联想到一起了,想着直接把追猎者与要塞合在一起进行排序,但仔细想想,有太多细节,然后放弃了。
完整代码:

#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;
const ll INF=1e18;
struct node{
	ll d,g;
}m1[N],sum[N],m[N];
bool cmd(node x,node y)
{
	return x.d<y.d;
}
bool cmd1(node x,node y)
{
	return x.g<y.g;
}
void slove()
{
	ll s,b;
	cin>>s>>b;
	for(ll i=1;i<=s;i++)
	{
		cin>>m[i].d;
		m[i].g=i;
	}
	for(ll i=1;i<=b;i++)
	{
		cin>>m1[i].d>>m1[i].g;
	}
	sort(m1+1,m1+b+1,cmd);//排序为了最大化前缀和
	sort(m+1,m+s+1,cmd);
	for(ll i=1;i<=b;i++)
	{
		sum[i].d=m1[i].d;
		sum[i].g=m1[i].g+sum[i-1].g;
	}
	ll x=1;
	for(ll i=1;i<=s;i++)
	{
		while(m[i].d>sum[x].d&&x<=b)//往后寻找第一个大于的防御力
		x++;
		if(m[i].d>=sum[x].d&&x<=b)
		m[i].d=sum[x].g;
		else
		m[i].d=sum[x-1].g;
		}
	sort(m+1,m+s+1,cmd1);//按最初的位置排序
	for(ll i=1;i<=s;i++)
	{
		cout<<m[i].d<<" ";
	}
}
signed main()
{
	IOS;
	ll t=1;
	//cin>>t;
	while(t--)
	slove();
	return 0;
}

当然还有更简便的就是前缀和加二分

#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=1e5+10;
const ll INF=1e18;

pii c[N];  // 存储防御力以及收获
ll sum[N];  // 前缀和数组
ll a[N];  // 存攻击力

// 二分查找函数
ll bs(ll x, ll l, ll r)
{
    while(l < r)
    {
        ll mid = l + r + 1 >> 1;
        if (c[mid].fi <= x) l = mid;
        else r = mid - 1;
    }
    return l;
}

void slove()
{
    ll s, b;
    cin >> s >> b;
    for (ll i = 1; i <= s; i++)
        cin >> a[i];
    for (ll i = 1; i <= b; i++)
        cin >> c[i].fi >> c[i].se;
    sort(c + 1, c + b + 1);
    
    // 计算前缀和
    for (ll i = 1; i <= b; i++)
        sum[i] = sum[i - 1] + c[i].se;
    
    // 对每个s的数值进行查询并输出结果
    for (ll i = 1; i <= s; i++)
    {
        ll id = bs(a[i], 0, b);//查找的是下标
        cout << sum[id] << " ";
    }
    cout << endl;
}

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

F.博弈

题目传送门:博弈
在这里插入图片描述
在这里插入图片描述
思路:
简单说,就是要你模拟 / 推导,对于数组的每个前缀,两个玩家按照 “一个想最大化、一个想最小化最终结果” 的策略博弈后,最终剩下的那个值是多少 。
要想最优,A同学必须选择两个偶数或者两个奇数,因为这两组相加之后还是偶数,这样就不会有损失,而B同学要想最小化,则必须是一奇一偶相加,这样才会损失1,由此,可以以3个奇数为一组,这样可以最优,由此,只需要统计各个阶段该区间内奇数的个数。
AC代码:

#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 sum[N];
ll a[N];
ll f[N];
void slove()
{
	ll n;
	cin>>n;
	if(n==1)
	{
		ll x;
		cin>>x;
		cout<<x<<endl;
		return ;
	}
	for(ll i=1;i<=n;i++)
	{
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
		if(a[i]%2!=0)
			f[i]++;
		f[i]=f[i]+f[i-1];//奇数个数前缀和
	}
	
	for(ll i=1;i<=n;i++)
	{
		if(i==1)//当区间为1时不需要进行操作,直接输出就行
		{
		cout<<sum[i]<<" ";
		continue;
		}
		if(f[i]%3==1)//如果最后还剩余一个奇数,则需要多减去1
		cout<<sum[i]-1-f[i]/3<<" ";
		else
		cout<<sum[i]-f[i]/3<<" ";
	}
}
signed main()
{
	IOS;
	ll t=1;
	// cin>>t;
	while(t--)
	slove();
	return 0;
}

G.方案数

题目传送门:方案数
在这里插入图片描述
思路:
就是组合数,以及求k与的关系,写这一题的时候太无语了,组合数会求,通过杨辉三角推,乘法逆元,也用上了,同时还试了快速幂,最后就是错在了这一句,
在这里插入图片描述
完整代码:
第一种:杨辉三角递推

#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=1e3+10;
const ll INF=1e18;
const ll mod=1e9+7;
ll s[N][N];
ll n,k,m;
void pre()
{
	for(ll i=1;i<=1001;i++)
	{
		s[i][0]=1;
		s[i][i]=1;
		for(ll j=1;j<i;j++)
		{
			s[i][j]=(s[i-1][j-1]+s[i-1][j])%mod;
		}
	}
}
void slove()
{
	pre();
	cin>>n>>k>>m;
	ll sum=1;
	for(ll i=1;i<=k;i++)
	{
		sum=(sum*m)%mod;
	}
	ll ans=0;
	ans=s[n][k]%mod;
	cout<<ans*sum%mod<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	//cin>>t;
	while(t--)
	slove();
	return 0;
}

第二种;

#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=1e3+10;
const ll INF=1e18;
const ll mod=1e9+7;
ll s[N][N];
ll n,k,m;
ll qmin(ll x,ll y)
{
	if(y==0)
	return 1;
	ll sum=1;
	while(y)
	{
		if(y&1)
		sum=sum*x%mod;
		x=x*x%mod;
		y/=2;
	}
	return sum;
}
ll jie(ll n1,ll k1)
{
	ll ans=1;
	for(ll i=n1;i>n1-k1;i--)
	{
		ans=(ans*i)%mod;
	}
	 for(ll i=2;i<=k1;i++)
	 {
		 ans=ans*qmin(i,mod-2)%mod;
	 }
	return ans;
}
void slove()
{
	cin>>n>>k>>m;
    ll sum=1;
    for(ll i=1;i<=k;i++)
        sum=(sum*m)%mod;
	ll ans=jie(n,k)%mod;
	ans=ans%mod;
	cout<<ans*sum%mod<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	//cin>>t;
	while(t--)
	slove();
	return 0;
}

H.连续合规子串

题目传送门:连续合规子串
在这里插入图片描述
在这里插入图片描述
写这一题时,同样也快被折磨的快吐了,
写的时候一直把两个条件并在一起,其实并非如此,先去寻找远的,如果不满足就找相邻的,如果不满足,同样跳过当前的字符
完整代码;

#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;
const ll INF=1e18;
void slove()
{
	ll n;
	cin>>n;
	string s;
	cin>>s;
	if(n==1)
	{
		cout<<1<<endl;
		return ;
	}
	if(n==2)
	{
		if(s[1]!=s[0])
		cout<<2<<endl;
		else
		cout<<1<<endl;
		return ;
	}
	ll ans=0;
	ll l=0;
	for(ll r=1;r<n;r++)
	{
		if(s[r]==s[r-2])
		l=r-1;
		if(s[r]==s[r-1])
		l=r;
		ans=max(ans,r-l+1);
	}
	cout<<ans<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	//cin>>t;
	while(t--)
	slove();
	return 0;
}

K.魔法音符

题目传送门:魔法音符
在这里插入图片描述
在这里插入图片描述
看见这一题时,第一眼就想到了接雨水,但是当时忘记了怎么写了,就琢磨了半天,好在最后写出来,要写不出来,就丢脸丢到家了,当比赛结束时,看了别人代码,才知道,原来,可以通过预处理来解决,嗯,又学到了一点。
完整代码:
第一种单调栈:

#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;
const ll INF=1e18;
ll s[N];
void slove()
{
	ll n;
	cin>>n;
	for(ll i=1;i<=n;i++)
	cin>>s[i];
	stack<ll>p;
	ll ans=0;
	p.push(1);
	for(ll i=2;i<=n;i++)
	{
		while(!p.empty()&&s[p.top()]<s[i])
		{
			ll r=i;
			ll x=p.top();
			p.pop();
			if(p.empty())
			break;
			ll h=min(s[p.top()]-s[x],s[i]-s[x]);
			ll l=p.top();
			ans=ans+(r-l-1)*h;
		}
		
		p.push(i);
	}
	cout<<ans<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	//cin>>t;
	while(t--)
	slove();
	return 0;
}

第二种预处理:

#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;
const ll INF=1e18;
ll a[N];
ll b[N];
ll c[N];
void slove()
{
	ll n;
    cin>>n;
    ll ans=0;
    for(ll i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    b[1]=a[1];
    for(ll i=1;i<=n;i++)
    {
        b[i]=max(b[i-1],a[i]);
    }
    c[n]=a[n];
    for(ll i=n-1;i>=1;i--)
    {
        c[i]=max(c[i+1],a[i]);
    }
    for(ll i=1;i<=n;i++)
    {
        ans+=min(b[i],c[i])-a[i];
    }
    cout<<ans<<endl;
}
signed main()
{
	IOS;
	ll t=1;
	//cin>>t;
	while(t--)
	slove();
	return 0;
}

总结

通过这场比赛,收获不少,写的时候,好像找到了,刚进入大一时对于一道题百思不得其解,最后通过答案,找到了自己的错误,那种新鲜感让人上瘾,希望这种感觉延迟下去。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值