牛客xiao白月赛61-D,E,F

D.

链接:https://ac.nowcoder.com/acm/contest/46597/D
来源:牛客网
 

酒足饭饱之后 PLMM 有些无聊,询问 The__Flash 要不要做什么有趣的事情,The__Flash 立即提议道当然是打游戏啦!

在 RA2 世界中,The__Flash 非常享受占领油田成为石油大亨。

初始地图上有一个兵营和 n 块油田,油田编号为 1,2,⋯,n。兵营中有无限个小兵,The__Flash 可以花费金额 ec,使得兵营在 et 秒过后培养出一名工程师,需要注意的是兵营同一时间只能培养一名工程师且一名工程师至多占领一块油田。工程师从兵营移动到第 i 块油田并将其占领的用时为 ti 秒。若第 i 块油田在第 t 秒末被 The__Flash 的工程师占领,则该油田从第 t+1 秒末起每秒末可以给 The__Flash 增加 p 的金额。

当前,The__Flash 持有初始金额 s,想要指派工程师按照油田编号从小到大的顺序依次占领所有油田。由于 The__Flash 忙着抢占油田,所以请你帮忙计算他需要的时间。

输入描述:

第一行输入 5 个整数,分别表示n, ec, et,p和s(1≤n,ec,et,p,s≤1e5)。

第二行输入 n 个整数,分别表示t1​,t2​,⋯,tn​(1≤ti​≤1e5) ,保证对于 i>1 都有 。

输出描述:

输出一个整数,表示 The__Flash 占领 nnn 个油田所需的时间,若不可能则输出 −1-1−1。

示例1

输入

2 1 5 5 2
2 5

输出

15

说明

第 0 秒:开始训练第 1 个工程师,金额为 1。
第 5 秒:第 1 个工程师训练完毕,开始训练第 2 个工程师,金额为 0。
第 7 秒:第 1 个工程师占领了第 1 块油田,金额为 0。
第 10 秒:第 2 个工程师训练完毕,金额为 15。
第 15 秒:第 2 个工程师占领了第 2 块油田,金额为 40。

示例2

输入

5 6 2 7 6
2 3 6 7 7

输出

20

示例3

输入

7 9 5 2 7
4 5 6 8 8 10 10

输出

-1

该题可以用优先队列解决。

首先可以直接得出如果最开始的金额s小于训练价格ec,那么则不可能占领田,所以输出-1,如果s>=ec则一定可以占领完所有的田。 

由于本题有多种事件(训练,占领等),每种事件的执行时间区间可能重叠,所以处理起来很复杂。

所以有一种简单的方法是模拟时间轴,枚举每个时间点,求出每个时间点需要执行什么事件,模拟该点需要执行的事件,最后求出占领完所有油田的时间点即可,又由于该题总的时间数据很大,所以肯定不能从0开始一个一个时间点的枚举。

但是该题事件的最大数量较小(最多训练n次,占领n次和当前钱不够等钱存够的n次,最多3n次),因此我们可以对每个事件进行分类,弄个最小堆的优先队列存入每个当前可以执行事件的类型和事件结束的时间点,根据事件结束的时间点进行排序,然后每次取出最小的结束时间点进行模拟即可。

我们可以把事件分成三类:
1.有钱可以训练
2.训练完成
3.占领完成

这题要注意的地方是,由于钱可能出现溢出的情况,比如:有1e5的钱,每秒获得1e5的钱,训练工程师花费1,训练时间是1e5秒,有1e5个油田,这时候如果对钱上限不加以限制,钱的总数会爆longlong, 所以要对钱的上限进行限制,由于训练费用最多为1e5,田最多只有1e5个,所以训练的工程师数量最多只要1e5就行,所以花费总的金钱不会超过1e5*1e5=1e10,所以将钱的总数上限设定到1e10即可。

具体流程看代码,代码如下:

#include<iostream>
#include<set> //这里用set进行最小堆的模拟,由于set内部实现是红黑树,所以有自动排序的功能 
using namespace std;
typedef long long LL;
typedef pair<LL, int> PLI; //第一个参数first存时间点,第二个参数second存事件类型 
const int N = 1e5+5;
const LL MAX = 1e10+5; //MAX当作最大的金额使用
int a[N]; //用来存去并占领每个油田所需时间 
set<PLI> se; //当最小堆使用 

//三种类型: 
// 0 有钱可以训练 
// 1 训练完成 
// 2 占领完成 
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	LL n, ec, et, p, s;
	cin >> n >> ec >> et >> p >> s;
	for(int i=1; i<=n; i++) cin >> a[i];
	if(s<ec) cout << -1 << endl; //如果初始金额小于训练金额 
	else
	{
		se.insert({0, 0}); //最开始时是类型0 
		
		//ans存答案,last存上一个事件结束时间点,man存工程师人数,train用来标记当前是否有人训练,有人在训练标1,反之0 
		//done表示当前所占领完的田数,ne表示下一个要占领的田 
		LL ans = 0, last = 0, man = 0, train = 0, done = 0, ne = 1;
		while(done<n)
		{
			PLI k = (*se.begin()); //取最小的事件结束的时间点 
			se.erase(se.begin());//删除最小的点 
			s = min(s+min(MAX, (k.first-last)*(done*p)), MAX); //更新s值,min用来保证新的s值不会超过MAX 
			if(k.second==1) //事件类型1,表示训练完成 
			{
				man ++; //工程师数量加一 
				train = 0; //标记为0,表示无人训练 
			}
			else if(k.second==2) done ++; //事件类型2,表示占领完成,done ++ 

			if(ne<=n && man) //当前没有占领完,并且还有空闲工程师的话 
			{
				se.insert({k.first+a[ne], 2}); //进行占领 
                ans = max(ans, k.first+a[ne]); //更新ans值 
                ne ++;
				man --;
			}
			if(s>=ec && !train) //如果当前没有人在训练,并且钱足够的话 
			{
				train = 1; //标记为1,表示有人训练 
				s -= ec; //减去费用 
				se.insert({k.first+et, 1}); //进行训练 
			}
			if(s<ec && done) //当前钱不够训练的状态,得等钱足够 
			{
				LL t = (ec-s)/(p*done)+((ec-s)%(p*done)!=0); //等钱足够所需时间,加上((ec-s)%(p*done)!=0是为了向上取整 
				se.insert({k.first+t, 0}); //当前钱足够的状态 
			}
			last = k.first; //更新last值 
		}
		cout << ans << endl;
	}
	
	return 0;
}

E.

链接:https://ac.nowcoder.com/acm/contest/46597/E
来源:牛客网
 

The__Flash 打游戏太投入了,把 PLMM 晾在了一边。为了弥补 PLMM,The__Flash 带着 PLMM 和小喵去看电影。

电影院人山人海,队伍排成一条长长的线,两人一喵只能乖乖排队。队伍长度为 n,个体按照队头到队尾的顺序依次编号为 1,2,...,n,其中第 i 个个体的身高为 ai​。PLMM 见状想要出题考验一下 The__Flash。

一个长度为 n 的序列 a 的逆序对个数定义为满足 ai​>aj​(1≤i<j≤n) 的不同 (i,j) 的对数,(i1,j1) 与 (i2,j2) 不同当且仅当 i1≠i2 或 j1≠j2​。

n 个个体总共有 n! 种排队方式,记 Pi(a) 表示序列 a 的第 i 种排队方式,cnt(Pi(a))表示 Pi 的逆序对个数。PLMM 想知道 \sum_{i=1}^{n!}cnt(P_{i}(a)),由于 The__Flash 傻乎乎的,所以请你帮他回答 PLMM 的问题。

输入描述:

第一行输入一个整数 n (1≤n≤1e5)。

第二行输入 n 个整数表示 a1​,a2​,⋯,an​(1≤ai​≤1e5)。

输出描述:

输出一个整数表示答案,由于结果可能太大,因此你只需要输出结果对 1e9+7 取模之后的结果。

示例1

输入

3
1 2 3

输出

9

示例2

输入

7
2 4 4 3 1 1 2

输出

45360

 该题每一种排队方法的逆序对数的期望=任取两个不相等数的情况 / 2, 除以2是因为两个不同数中,大的在前和小的在前的情况数是相等,而逆序对是大的数在前所以要除以2。

任取两个不相等数的情况数 = 任取两个数的情况数 - 取得两个数相等的情况数,即 C_{n}^2-\sum C_{num}^{2} (num为相等那个数在排队中的个数),所以一种排队方法的逆序对数量的期望为:\frac{C_{n}^2-\sum C_{num}^2}{2}

而对于n!种排队个数的总逆序对数就是  \frac{n! \times (C_{n}^2-\sum C_{num}^{2})}{2} 。

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL MOD = 1e9+7, N = 1e5+5;
LL a[N], p[N];
LL Qpower(LL a, LL b) //快速幂求逆元 
{
    LL res = 1;
    while(b)
    {
        if(b&1) res = res % MOD * a % MOD;
        
        b >>= 1;
        
		a = a % MOD * a % MOD;
    }
    return res % MOD;
}
LL C(LL a, LL b) //C(a, b)
{
	if(a<b) return 0;
	return (p[a]%MOD*Qpower(p[b]%MOD*p[a-b]%MOD, MOD-2)%MOD)%MOD;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    LL n, u = 0;
    cin >> n;
    p[0] = 1;
    for(int i=1; i<=n; i++) p[i] = (p[i-1] % MOD * i % MOD) % MOD; //求出阶乘

    int last = -1, cnt = 1;
    for(int i=0; i<n; i++)
		cin >> a[i];
    sort(a, a+n);
	for(int i=0; i<=n; i++) //求出两个数相等的情况数 
	{
		if(a[i]!=last)
		{
			u = u % MOD + C(cnt, 2) % MOD;
			last = a[i];
			cnt = 1;
		}
		else cnt ++;
	}
	//根据公式求出答案,这里由于式子太多,所以分部写的 
    LL res = (p[n]%MOD * Qpower(2*p[n-2], MOD-2)%MOD)%MOD;
    res = (res % MOD - u % MOD + MOD) % MOD;
    res = p[n]%MOD * res % MOD;
    res = (Qpower(2, MOD-2)%MOD * res % MOD ) % MOD;
    
    cout << res << endl;
    return 0;
}

F.

链接:https://ac.nowcoder.com/acm/contest/46597/F
来源:牛客网
 

两人一喵终于排到售票处,准备选择放映厅的座椅。

放映厅中一共有 n 张座椅,顺序拍成一排,依次编号为 1,2,...,n。由于群居效应,观影者总是会选择与其他观影者相邻的座椅,即观影者的座椅是序列 [1,2,...,n] 的一段子区间。此外,两人一喵对座椅还有其他要求,分别给出了长度为 m 的序列 a,b 和 c,表示 ai​,bi ci​(1≤i≤m) 三个座椅中至少有一个座椅被观影者使用。

记 f(x) 表示放映厅中有 x 位观影者的座椅使用方案数。The__Flash 想知道 f(1),f(2),...,f(n)。

由于 The__Flash 忙着跟 PLMM 贴贴,所以请你来回答这个问题。

输入描述:

第一行输入两个整数 n(1≤n≤1e5) 和 m(1≤m≤1e5)。

第二行输入 m 个整数表示 a1​,a2​,...,am​(1≤ai​≤n)。

第三行输入 m 个整数表示 b1​,b2​,...,bm​(1≤bi​≤n)。

第四行输入 m 个整数表示 c1​,c2​,...,cm​(1≤ci​≤n)。

输出描述:

输出 n 个整数,以空格分隔,分别表示 f(1),f(2),...,f(n),由于结果可能太大,所以你只需要输出结果对 1e9+7 取模后的结果。

示例1

输入

3 2
1 1
1 2
2 2

输出

2 4 6

示例2

输入

4 6
2 2 2 2 2 3
3 1 1 1 2 2
2 2 2 4 3 4

输出

1 4 12 24

示例3

输入

9 3
9 2 6
7 5 6
1 5 3

输出

0 0 12 72 480 2880 15120 80640 362880

该题运用了双指针和差分的思想。首先由于观众只会连续的坐,所以对于x名观众的座位使用方法数f(x) = x!*长度为x的区间个数。同时又要求在三个长度为m的序列a,b,c中,对于每个座位ai,bi或ci,至少存在一个座位被观众使用。这相当于有m个条件,每个条件i都要求座位ai,bi,ci至少存在一个座位被使用,所以f(x) = x! * 长度为x并且满足m个条件的区间个数。 

又可以得知如果区间[l, r]满足m个条件,那么[l, r+1], [l, r+2], ..., [l, n]的区间也一定满足条件。所以对于长度在 [r-l+1, n-l+1]区间内满足条件的区间个数 +1,对于这种在某个区间范围内所有数的加1,可以直接用差分维护。 

因此只要枚举每个l,找到最小的r能够使[l, r]满足m个条件即可。这样就可以把所有长度中满足m个条件的区间个数给求出来了。

具体过程看代码,代码如下:

#include<iostream>
#include<vector> 
using namespace std;
typedef long long LL;
const int N = 1e5+5, MOD = 1e9+7;
vector<int> v[N]; //v[i]用来存编号为i的座位所在的那些条件 

//p用来存阶乘,cnt[i]表示第i个条件中的三个座位有几个座位被使用,now表示当前满足的条件的个数,ans存答案 
LL p[N], cnt[N], now = 0, ans[N] {0};

void add(int id) //每加入一个座位,更新条件满足的个数 
{
	for(auto &i: v[id]) //遍历编号为id的座位所在的所有条件 
	{
		cnt[i] ++; // 条件i中的一个座位被使用,所以cnt[i] ++; 
		if(cnt[i]==1) now ++;  //如果cnt[i]==1, 则表示该条件i刚好被满足,所以条件满足数now ++;
	}
}
void del(int id) //删除一个座位,更新条件满足的个数 
{
	for(auto &i: v[id])
	{
		cnt[i] --; //条件i中的一个座位不被使用了,所以cnt[i] --; 
		if(!cnt[i]) now --; //如果cnt[i]==0了,则表示条件i不再被满足了,所以now --; 
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int n, m;
	cin >> n >> m;
	for(int i=1; i<=m; i++)
	{
		int x;
		cin >> x;
		v[x].push_back(i); //存x号座位所在的条件i 
	}
	for(int i=1; i<=m; i++)
	{
		int x;
		cin >> x;
		v[x].push_back(i);
	}
	for(int i=1; i<=m; i++)
	{
		int x;
		cin >> x;
		v[x].push_back(i);
	}
	p[0] = 1;
	for(int i=1; i<=n; i++) p[i] = (p[i-1]%MOD * i) % MOD; //求阶乘 
	int j = 1;
	for(int i=1; i<=n; i++) //枚举l 
	{
		while(now!=m && j<=n) //当前区间不满足且j<=n的情况 
		{
			add(j); //加入当前座位 
			j ++;
		}
		if(now==m) //如果条件满足,对于长度在区间[r-l+1, n-l+1]的区间个数+1 
		{
			ans[j-1-i+1] ++; //注意j=r+1,所以要减一 
			ans[n-i+1+1] --;
		}
		del(i); //删除l 
	}
	for(int i=1; i<=n; i++) ans[i] += ans[i-1];
	for(int i=1; i<=n; i++)
	{
		cout << (ans[i]*p[i])%MOD << " ";
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值