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

样例解释:

image

整场比赛最水的一题 但是我好像只写了个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\) 个块的方案有重叠部分,所以在统计方案的时候要使用容斥原理。最终答案就是如下式子:

\[\sum_{i=1}^{\lfloor \frac{k}{r} \rfloor} (-1)^i C_{n-k+1}^i C_{n-ir}^{k-ir} \]

\(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] 游览计划 斯坦纳树,学长讲了之后一头雾水……没敲代码。

posted @ 2025-08-10 21:36  wwwidk1234  阅读(19)  评论(0)    收藏  举报