当前位置: 首页 > news >正文

2025“钉耙编程”中国大学生算法设计暑期联赛(5)

切排列

题意简化

给定两个长度为 $ n $ 的 1~n的排列 $ A $ 和 $ B $,需将 $ A $ 切成若干段,再按任意顺序拼接得到 $ B $,求最少需要切的段数 $ k $ 的最小值。

数据范围

  • 测试组数 $ T : 1 \leq T \leq 10^4 + 5 $;
  • 每组排列长度 $ n : 1 \leq n \leq 2 \times 10^5 $;
  • 所有测试用例的 $ n $ 之和:$ \sum n \leq 5 \times 10^5 + 16 $;
  • $ A 、 B $ 均为$ 1 \sim n $的排列(每个数恰好出现一次)。

思路

直接贪心,枚举a中每一个数,检验他在b数组中是否连续

点击查看代码
#include<bits/stdc++.h>using namespace std;
int t;
const int maxn=2e5+10; 
int n;
int a[maxn],b[maxn];
int c[maxn]; void solve(){cin>>n;for(int i=1;i<=n;++i){cin>>a[i];c[a[i]]=i;} for(int i=1;i<=n;++i) cin>>b[i];int ans=0;for(int i=1;i<=n;++i){while(c[b[i]]==c[b[i+1]]-1){++i;}++ans;}cout<<ans<<endl;return ; 
}int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>t;while(t--){solve();}	return 0;
} 

k-MEX

题意简化

给定正整数 $ n $ 和 $ k $,从 $ 0,1,\dots,n-1 $ 中随机选取 $ k $ 个不同的整数组成集合,求该集合的 MEX(最小未出现的自然数) 的期望。结果以最简分数 $ \frac{x}{y} $ 形式给出,需对 $ 10^9+7 $ 取模后输出。

数据范围

  • 测试用例组数 $ T : 1 \leq T \leq 10^4 + 7 $;
  • 每组的 $ n,k : 1 \leq k \leq n \leq 10^9 $。

思路

要计算从 $ 0,1,\dots,n-1 $ 中选 $ k $ 个不同整数的集合的 MEX(最小未出现自然数) 期望,核心过程如下:

步骤1:期望的转换

对于非负整数随机变量 $ X = \text{MEX} $,期望满足 $ E[X] = \sum_{i=1}^{\infty} P(X \geq i) $(因为 $ \text{MEX} $ 最大为 $ k $,只需求和到 $ i=k $)。

步骤2:分析 $ P(\text{MEX} \geq i) $

若 $ \text{MEX} \geq i $,则集合必须包含 $ 0,1,\dots,i-1 $ 所有元素。此时:

  • 需从剩余 $ n - i $ 个元素($ i $ 到 $ n-1 $)中选 $ k - i $ 个,选法数为 $ \binom{n-i}{k-i} $;
  • 总选法数为 $ \binom{n}{k} $(从 $ n $ 个中选 $ k $ 个)。

因此:
$ P(\text{MEX} \geq i) = \frac{\binom{n-i}{k-i}}{\binom{n}{k}} $

步骤3:化简求和式

对 $ i=1 $ 到 $ k $ 求和 $ \sum_{i=1}^{k} \frac{\binom{n-i}{k-i}}{\binom{n}{k}} $,重点化简分子部分 $ \sum_{i=1}^{k} \binom{n-i}{k-i} $。

利用组合数累加性质(杨辉三角递推:$ \binom{m}{r} + \binom{m}{r+1} = \binom{m+1}{r+1} $),逐步累加后,分子和最终化简为 $ \binom{n}{k-1} $。

步骤4:计算组合数比值

$ 分子为 \binom{n}{k-1} ,分母为 \binom{n}{k} $

利用组合数公式化简:
$ \frac{\binom{n}{k-1}}{\binom{n}{k}} = \frac{\frac{n!}{(k-1)!(n-k+1)!}}{\frac{n!}{k!(n-k)!}} = \frac{k}{n - k + 1} $

最终结论

MEX的期望为:
$ \boxed{\frac{k}{n - k + 1}} $

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod=  1e9 + 7;ll power(ll a, ll b) {ll res = 1;for (; b; b /= 2, a = a * a % mod) if (b & 1) res = res * a % mod;return res;
}void solve() {ll n, k;cin >> n >> k;cout << power(n - k + 1, mod - 2) * k % mod << "\n";
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int _ = 1;cin >> _;while (_--) {solve();}return 0;
}

最小值

题意

给定 $ T $ 组数据,每组包含两个长度为 $ n $ 的整数数组 $ a $ 和 $ b $,需找出所有满足 $ p \neq q $ 的下标对,计算 $ \left| \left| a_p - a_q \right| - \left| b_p - b_q \right| \right| $ 的值,并求出这些值中的最小值

数据范围

  • 测试组数 $ T \(:\) 1 \leq T \leq 10^4 $;
  • 每组数组长度 $ n \(:\) 2 \leq n \leq 10^5 $,且所有测试用例的 $ n $ 之和 $ \sum n \leq 5 \times 10^5 $;
  • 数组元素绝对值:$ |a_i| \leq 10^{12} \(,\) |b_i| \leq 10^{12} $。

思路

步骤1:表达式转换(分类讨论)

通过对 $ a_p 与 a_q 、 b_p 与 b_q $ 的大小关系分类讨论,可证明:

\[\left| |a_p - a_q| - |b_p - b_q| \right| = \min\left\{ \left| (a_p + b_p) - (a_q + b_q) \right|, \left| (a_p - b_p) - (a_q - b_q) \right| \right\} \]

具体分类(核心逻辑):

  • 当 $ (a_p - a_q)(b_p - b_q) \geq 0 $ 时,$ \left| |a_p - a_q| - |b_p - b_q| \right| = \left| (a_p - b_p) - (a_q - b_q) \right| $;
  • 当 $ (a_p - a_q)(b_p - b_q) < 0 $ 时,$ \left| |a_p - a_q| - |b_p - b_q| \right| = \left| (a_p + b_p) - (a_q + b_q) \right| $;
    且进一步可证,原表达式是这两个新表达式的较小值

构造新数组并排序

根据上述结论,构造两个新数组:

  • $ A_i = a_i + b_i $
  • $ B_i = a_i - b_i $

对数组 $ A $ 和 $ B $ 分别排序(排序后,数组中“最小的绝对差”一定出现在相邻元素之间)。

找相邻元素的最小差

对排序后的 $ A $ 和 $ B $,分别计算相邻元素的绝对差,并记录各自的最小差

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;void sovle()
{int n;cin>>n;vector<ll> a(n),b(n),j,k;for(int i=0;i<n;i++)cin>>a[i];for(int i=0;i<n;i++)cin>>b[i];for(int i=0;i<n;i++){j.push_back(a[i]+b[i]);k.push_back(a[i]-b[i]);}sort(j.begin(),j.end());sort(k.begin(),k.end());ll minj=1e15,mink=1e15;for(int i=1;i<n;i++){if(j[i]-j[i-1]<minj)minj=j[i]-j[i-1];if(k[i]-k[i-1]<mink)mink=k[i]-k[i-1];}cout<<min(minj,mink)<<endl;
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int T;cin >> T;while (T--) {sovle();}return 0;
}

随机反馈

题意

在一场时长为 $ n $ 分钟的编程比赛中,每分钟提交一次正确代码。第 $ i $ 分钟提交的正确代码,有 $ p_i = \frac{x_i}{1000} $ 的概率被判 WA,$ 1 - p_i $ 的概率被判 AC(且第 $ n $ 分钟提交的代码必判 AC,即 $ x_n = 0 $)。

罚时定义:若第一次得到 AC 的提交时间为 $ t $,则罚时为 $ t + 20 \times (t - 1) $( t-1 是之前的提交次数);若始终未 AC,罚时为无穷大。需计算最小的罚时期望。

数据范围

  • 测试组数 $ T $:正整数。
  • 每组比赛时长 $ n : 1 \leq n \leq 10^5 $,所有测试用例的 $ n $ 之和 $ \leq 5 \times 10^5 $。
  • 每组的 $ x_1, x_2, \dots, x_n $:满足 $ 0 \leq x_i \leq 1000 $,且 $ x_n = 0 , p_i = \frac{x_i}{1000} $。

思路

要推导比赛罚时期望的通用计算公式,可通过递推法分析:

1. 问题建模

  • 第 $ i $ 分钟提交正确代码时,得 AC 的概率为 $ a_i = 1 - p_i $(其中 $ p_i = \frac{x_i}{1000} $),且第 $ n $ 分钟必 AC($ x_n = 0 $,故 $ a_n = 1 $)。
  • 若第一次 AC 在第 $ t $ 分钟,罚时为 $ \text{罚时}_t = t + 20(t - 1) = 21t - 20 $。

2. 递推关系推导

定义 $ f(t) $ 为从第 $ t $ 分钟开始提交的期望罚时。需满足:

  • 边界条件:第 $ n $ 分钟必 AC,故 $ f(n) = 21n - 20 $。
  • 递推式($ t < n $ 时):第 $ t $ 分钟有 $ a_t $ 概率 AC(罚时 $ 21t - 20 $),有 $ p_t $ 概率 WA(后续从 $ t+1 $ 分钟开始,期望罚时为 $ f(t+1) $)。因此:

    \[f(t) = a_t \cdot (21t - 20) + p_t \cdot f(t+1) \]

    其中 $ a_t = 1 - p_t , p_t = \frac{x_t}{1000} $。

3.结论

最小期望罚时可通过从后往前的线性递推计算,递推式为:

\[f(t) = \begin{cases} 21n - 20, & t = n \\ (1 - p_t)(21t - 20) + p_t \cdot f(t+1), & t < n \end{cases} \]

最终结果为 $ f(1) $,时间复杂度 $ O(n) $,可高效处理 $ n \leq 10^5 $ 的数据。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int n,m,k;
const int maxn=2e5+10; 
double f[maxn];
int x[maxn];
void sovle(){cin>>n;for(int i=1;i<=n;++i) f[i]=1e9;for(int i=1;i<=n;++i) cin>>x[i]; f[n]=n; for(int i=n-1;i>=1;--i){double p=x[i]*1.0/1000.0;f[i]=min(f[i+1],(1.0-p)*i+p*(f[i+1]+20));}double ans=1e9;for(int i=1;i<=n;++i) ans=min(ans,f[i]);cout<<fixed<<setprecision(10)<<ans<<endl;return ;
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int T;cin >> T;while (T--) {sovle();}return 0;
}

信赖跃迁:置换轨迹计算

题意简化

给定长度为 $ n $ 的置换 $ q $,以及长度为 $ m $ 的整数序列 $ k_1, k_2, \dots, k_m $,需计算满足以下条件的长度为 $ m+1 $ 的置换序列 $ p_0, p_1, \dots, p_m $ 的数量:

\[p_0 \circ p_1^{k_1} \circ p_2^{k_2} \circ \dots \circ p_m^{k_m} = q \]

(其中 $ \circ $ 表示置换的复合运算,$ p_i^{k_i} $ 表示置换 $ p_i $ 的 $ k_i $ 次幂)。

数据范围

  • 测试组数 $ t : 1 \leq t \leq 10 $;
  • 每组中置换长度 $ n : 1 \leq n \leq 10^5 $;
  • 每组中序列长度 $ m : 1 \leq m \leq 10^5 $;
  • 置换 $ q $:由 $ 0 \sim n-1 $ 组成的有效置换(元素不重复);
  • 序列元素 $ k_i : 1 \leq k_i \leq 10^5 $。

思路

利用$ p_0 $ 由其余置换唯一确定”的性质,将问题转化为计算 $ m $ 个任意置换的组合数,最终得到 $ (n!)^m $ 的模结果。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 1e9 + 7;vector<ll> F(100005);void pre() {F[0] = 1;for (int i = 1; i <= 1e5; i++) {F[i] = F[i - 1] * i % mod;}
}ll power(ll a, ll b) {ll res = 1;for (; b; b /= 2, a = a * a % mod) if (b & 1) res = res * a % mod;return res;
}void solve() {ll n, m;cin >> n >> m;vector<ll> a(n), b(m);for (ll i = 0; i < n; i++) {cin >> a[i];}for (ll i = 0; i < m; i++) {cin >> b[i];}cout << power(F[n], m) << "\n";
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);pre();int _ = 1;cin >> _;while (_--) {solve();}return 0;
}

题意简化

需给 $ n $ 人发放总金额为 $ m $ 元的报酬,每月发放一次。为避税,需满足三个条件:

  • 每人单笔收入严格不超过 $ k $ 元;
  • 每月发放人数严格不超过 $ p $ 人;
  • 每人累计收入严格不超过自身的 $ a_i $ 元(即最终收到的总钱数 $ \leq a_i $)。
    求收完所有报酬的最少月数

数据范围

  • 测试组数 $ T : 1 \leq T \leq 10004 $;
  • 每组数据:
    • 人数 $ n $、总报酬 $ m $、单笔最大金额 $ k $、每月最多发放人数 $ p : 1 \leq p \leq n \leq 2 \times 10^5 , 1 \leq k \leq m \leq 10^9 $;
    • 每人的“缴税前剩余可收金额” $ a_1, a_2, \dots, a_n : 1 \leq a_i \leq 10^9 $,且 $ \sum a_i \geq m $;
  • 单个测试点中,所有组的 $ n $ 之和 $ \sum n \leq 4 \times 10^5 + 14 $。

思路

很明显时间足够长一定是可以不交税的,时间很短也是必交税的
因此考虑二分答案
检验:
贪心策略最大化每月发放金额
验证某月数 mid 是否可行时,核心是计算该月数内 最多能发放的总金额,并与 m 比较。为最大化总金额,采用两步贪心:

  • 优先发放“整 k 元”:每人每月最多发 k 元,优先计算所有人在mid 个月内最多能被发放 整 k 元 的总次数(最大化单次发放金额)。

  • 用大额零钱填补空闲名额:若整 k 元的发放次数未填满每月 p 人的名额,则用每人剩余的零钱$ a_i % k $填补,且优先使用最大的零钱(通过提前降序排序实现),充分利用空闲名额。

预处理优化验证效率
对每人的零钱($ a_i % k $)提前降序排序,确保在填补空闲名额时能快速获取最大零钱,减少验证阶段的计算成本,使整体算法高效适配大数据范围。

点击查看代码
#include<bits/stdc++.h>using namespace std;
int t;
#define int long long  
const int maxn=2e5+10; 
using ll=long long ;int n,m,p,k;
int a[maxn];bool check(int  mid){int cnt=0;//所有人可以给cnt次k元 在所有月份中 vector<int>s;for(int i=1;i<=n;++i){if(a[i]>=mid*k) cnt+=mid;//可以每个月都给这人k else cnt+=a[i]/k,s.push_back(a[i]%k);	//这个人只能给有限次月 }// 如果每个月都能给p个人k元  if(cnt>=mid*p) return mid*p*k>=m;ll sum=cnt*k;//否则,还能在未选够p个人的月份,选入剩下的 for(int i=0;i<min(mid*p-cnt,(int)s.size());++i){sum+=s[i];} return sum>=m;} 
void solve(){cin>>n>>m>>k>>p;for(int i=1;i<=n;++i) cin>>a[i];sort(a+1,a+1+n,[](auto x,auto y){return x%k>y%k;});int l=1,r=1e9;while(l<=r){int mid=(l+r)>>1;if(check(mid)) r=mid-1;else l=mid+1; }cout<<l<<endl;return ;
}signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>t;while(t--){solve();}	return 0;
} 
http://www.hskmm.com/?act=detail&tid=31911

相关文章:

  • 斑马日记2025.10.15
  • 数据库查询通信开销降低97%的技术方案
  • 人生的底色
  • 差分操作正确性证明
  • json请求字符串格式化或使用转义字符
  • Rokid Glasses语音交互特性分析和复刻“乐奇” 唤醒词的方案简述
  • C++_设计模式
  • CF2143D2
  • 结果(Results)和结论 (Conclusion)的联系与区别
  • 【训练技巧】PyTorch多卡训练模型DistributedDataParallel和DataParallel设置方法详解及分布式训练命令解释 - 实践
  • 软件工程学习日志2025.10.15
  • newDay11
  • 向下填充(间断性)
  • 20251015
  • java date 初始化指定时分秒及比较日期大小
  • 轻量级ChatGPT克隆版nanochat技术解析
  • 10.15 —— 2020icpc上海D
  • [QOJ888] Travel around China 题解
  • MySQL面试必考:从入门到精通的20个问题
  • 手撕大模型 | MQA 和 GQA 原理解析
  • P1912 [NOI2009] 诗人小G 分析
  • [COCI2022-2023#2] Tramvaji 题解
  • 一级指针和二级指针作为函数参数的区别
  • ROUGE指标
  • CSP-S 模拟 29
  • Linux 文件及相关安全操作指南
  • day012
  • 怎么能把一个横着的很长的excel表,输出成一个能完整展示在一个页面中的PDF
  • 高精度
  • 深入解析:Leetcode+Java+图论+岛屿问题