2025 暑假集训 Day5
2025.8.8
Day5 NOIP 模拟赛,难度紫黄蓝紫,抽象的是一整场比赛全磕 T1T4 去了,T2 大水题写了个特殊性质然后就跑路了……喜提非 \(0\) 最低分 \(30+20+50+0=100\) 排名 \(30/47\)。
A. 序列
洛谷原题 P4402 [CERC2007] robotic sort 机械排序 平衡树板子,不会敲。
B. 跨年晚会
【题目】 一年一度的跨年晚会又要开始举行了,跨年晚会要邀请n位嘉宾分别表演节目,每位嘉宾由于节目以及类型的不同,所需要的表演时间Ai和台下准备时间Pi都不同,在准备期间,为了不让观众感到无聊,主持人需要讲一些段子来度过这些准备时间,一个段子需要固定的t分钟,如果时间不够t分钟,主持人只能聊聊天来度过这段时间了。
跨年晚会总共持续m分钟,作为制作人的你,需要给节目安排一个顺序,当然需要保证每个节目之前至少要有Pi分钟用来准备,并且后一位嘉宾的准备时间和前一位的表演时间不能相互重叠,那么这场晚会可以正常进行吗,如果可以的话,最多能讲多少个段子呢?
如果节目可以正常进行,输出一个整数表示最多能讲的段子数,否则输出-1。n,Ai,Pi,t<=1000,m<=10^8。
【样例输入】
3 30 2
2 3 5
5 2 4
【样例输出】
10
样例解释:

整场比赛最水的一题 但是我好像只写了个t=1的30pts做法然后就跑路了结果还挂了10pts
首先如果
可以发现,准备时间为 \(p_i\) 的节目是可以在准备时间内讲 \(\lfloor \dfrac{p_i}{t} \rfloor\) 个段子。除掉表演和准备的时间之后还剩下 \(m^\prime=m-\sum a_i-\sum p_i\) 的时间。可以发现,对于准备时间为 \(p_i\) 的节目有 \(k_i=p_i \bmod t\) 的空余时间啥都不能干的,如果再补上 \(t-k_i\) 的时间就可以再讲一个段子。于是可以考虑贪心地将 \(m^\prime\) 的剩余时间分配给这些节目,按照 \(k_i\) 从大到小排序,如果剩余时间够那么就分配给这个节目多讲一个段子。注意分配完之后还有多余的时间也得算上。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N=1007;
int n,m,t,a[N],p[N],Sum=0;
int k[N];
int main()
{
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m>>t;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
cin>>p[i];
Sum+=a[i]+p[i];
}
if(Sum>m)
{
cout<<"-1";
return 0;
}
int ans=0;
for(int i=1;i<=n;i++)
{
m-=a[i]+p[i];
ans+=p[i]/t; //准备时间讲段子
k[i]=p[i]%t; //准备时间还差一点的
}
sort(k+1,k+n+1,greater<int>());
for(int i=1;i<=n;i++)
{
int use=t-k[i];
if(m>=use)
{
m-=use;
ans++;
}
else break;
}
ans+=m/t;
cout<<ans;
return 0;
}
/*
先把表演和准备的时间算个总和Sum
然后对于第i个节目对答案的贡献就是准备时间除以段子时间向下取整
然后把一些差一点就能讲一个段子的节目用一些边角料时间补上去
按照准备时间p[i]%t排列(k[i]=p[i]%t),从大到小给时间
剩下的时间就继续讲段子了
*/
C. 数星星
天上有 \(n\) 颗星星,它们排成一排,从左往右以此编号为 \(1\sim n\),你需要在这 \(n\) 颗星星中选出 \(k\) 颗来进行观测,并在这 \(k\) 颗星星中,至少存在 \(r\) 颗星星的编号是连续的。输出方案数对 \(10^9+7\) 取模的结果。数据范围 \(r \le k \le n \le 10^7\)。
【样例1输入】
4 3 2
【样例1输出】
4
【样例2输入】
20 15 12
【样例2输出】
336
部分分给的很足,\(n \le 20\) 暴力给了 30pts,\(r=1\) 直接输出 \(C_n^k\) 再给 20pts。C 题出题人真良心。
定义 \(r\) 个连起来的星星为一个块。正难则反,我们不考虑取出星星,考虑放回。取出 \(k\) 颗星星等于剩下 \(n-k\) 颗星星,使用隔板法,此时有 \(n-k+1\) 个空。如果在里面选择 \(i\) 个空放回块,则方案数为 \(C_{n-k+1}^i\).放回了 \(i\) 个块,即 \(ir\) 颗星星,此时还剩下 \(n-kr\) 个位置可以丢 \(k-ir\) 个星星回去,方案数为 \(C_{n-ir}^{k-ir}\).
所以放回 \(i(i>0)\) 个块的方案数为 \(C_{n-k+1}^i C_{n-ir}^{k-ir}\).
注意,放回 \(i\) 个块的方案有重叠部分,所以在统计方案的时候要使用容斥原理。最终答案就是如下式子:
\(O(n)\) 预处理一下 \(i\) 的阶乘 \(i!\) 和 \(i!(1 \le i \le n)\) 的逆元就可以实现 \(O(1)\) 求组合数 \(C_n^k\)。注意在预处理的时候那个数组不能开 long long 否则会爆空间(空间限制是 128 MiB)
还要注意一下,笔者之前使用的是从前往后 \(O(n \log n)\) 的处理 \(i!\) 的逆元的方法(invf[0]=1;for(int i=1;i<=n;i++) invf[i]=invf[i-1]*ksm(i,mod-2)%mod; 其中 invf[i] 表示 \(i!\) 的逆元),其实是有一种更优的方案,就是先算出 \(n!\) 然后从后往前算,具体做法在代码里面。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
constexpr int N=1e7+7;
constexpr ll mod=1e9+7;
int n,k,r;
int f[N],invf[N]; //预处理阶乘和阶乘逆元
inline ll ksm(ll a,ll b)
{
ll s=1;
while(b)
{
if(b&1) s=s*a%mod;
a=a*a%mod;
b>>=1;
}
return s;
}
inline ll C(ll n,ll k) //计算C(n,k)
{
if(n<k) return 0;
return 1ll*f[n]*invf[k]%mod*invf[n-k]%mod;
}
int main()
{
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
cin>>n>>k>>r;
f[0]=invf[0]=1;
for(int i=1;i<=n;i++) f[i]=1ll*f[i-1]*i%mod;
invf[n]=ksm(f[n],mod-2);
for(int i=n-1;i>=1;i--) invf[i]=1ll*invf[i+1]*(i+1)%mod;
ll ans=0;
for(int i=1,opt=1;i<=k/r;i++,opt=-opt) ans=(1ll*ans+1ll*opt*C(n-k+1,i)%mod*C(n-i*r,k-i*r)%mod)%mod;
ans=(ans+mod)%mod;
cout<<ans%mod;
return 0;
}
D. 修复长城
洛谷原题 P4294 [WC2008] 游览计划 斯坦纳树,学长讲了之后一头雾水……没敲代码。

浙公网安备 33010602011771号