150分

集合均值

【题目描述】

\(3s,512MB\)

有两个可重集合\(A,B\) ,初始时 \(A\) 只包含一个 \(0\)\(B\) 是给定的。

执行以下操作:

  1. \(B\) 中随机选一个数 \(y\) ,把 \(y\)\(B\) 移动到 \(A\)
  2. 给答案加上\(A\) 的平均值。
  3. \(B\) 非空,回到步骤 \(1\)

求最后答案的期望。

【输入格式】

第一行一个整数 \(n,m\) ,表示集合\(B\) 可以分成 \(m\) 个大小为 \(n\) 的部分。

接下来一行 \(n\)个数表示一个部分,每个部分的数相同。

【输出格式】

一行一个整数表示答案模 \(998244353\) 的结果。

【样例 \(1\) 输入】

2 1
1 2

【样例 \(1\) 输出】

249561090

【样例 \(2\) 输入】

3 3
1 2 3

【样例 \(2\) 输出】

346216508

【样例解释】

样例一说明:

有两种可能,答案的期望为\(\frac 1 2(\frac 3 2+2)=\frac 7 4\)

样例二说明:

该样例中集合 \(B\)\(\{1,2,3,1,2,3,1,2,3\}\)

【数据范围】

对于 \(40\%\) 的数据:\(n=1\)

对于 \(70\%\) 的数据:\(n \times m \le 10^5\)

对于所有数据:\(n \times m \le 2\times 10^7,1 \le n \le 10^5, 1 \le m \le 2\times 10^7\)集合中的数是\([0,10^9]\) 内的整数。

【问题分析】

\(40\) 分:

\(n=1\) ,意味着 \(m\) 个大小为 \(1\) 的相同部分,即 \(m\) 个相同的数。

设该数为 \(a\) ,取出每一个数直到取完总共有 \(A_m^m\) 种方案,每一种方案的概率为 \(\prod_{i=m}^1 \frac 1 i=\frac 1 m\times \frac 1 {m-1}\times ...\times \frac 1 2\) ,由于 \(m\) 个数相同,每种方案最后的答案均相同,为 \(\sum_{j=2}^{m+1}\frac {(j-1)a} j=\frac a 2+\frac {2a} 3+...+\frac {ma} {m+1}\)

因此最后的答案 \(ans=A_m^m\times \prod_{i=m}^1\times \sum_{j=2}^{m+1}\frac {(j-1)a} j\) = \(\sum_{j=2}^{m+1}\frac {(j-1)a} j\)

设模数为 \(M\), 如果分别计算 \(\frac a 2\%M+\frac {2a} 3\%M+...+\frac {ma} {m+1}\%M\) ,时间复杂度为 \(O(m\log M)\) ,会超时。

需要使用线性求逆元(https://www.cnblogs.com/strve/p/13787783.html),时间复杂度为 \(O(m)\)

\(40\) 分参考程序】

//超时
#include<bits/stdc++.h>		
using namespace std;
typedef long long ll;
const int M=998244353;
const int N=100100;
int n,m;
ll a[N];
ll ksm(ll t,int q)
{
	ll tmp=1;
	while(q)
	{
		if(q&1) tmp=(tmp*t)%M;
		q>>=1;
		t=(t*t)%M;
	}
	return tmp;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	if(n==1)
	{
		ll ans=0;
		for(ll i=2;i<=m+1;i++)
			ans=(ans+(((i-1)*a[1])%M)*ksm(i,M-2))%M;//费马小定理求逆元 
		printf("%lld\n",ans);
	}
	return 0;
} 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=998244353;
const int N=100100;
int n,m;
ll a[N],inv[200*N];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	if(n==1)
	{
		inv[1]=1;
		for(int i=2;i<=m+1;i++)			//线性求逆元
			inv[i]=((M-M/i)*inv[M%i])%M;
		ll ans=0;
		for(ll i=2;i<=m+1;i++)
			ans=(ans+(((i-1)*a[1])%M)*inv[i])%M;
		printf("%lld\n",ans);
	}
	return 0;
} 

聚烷撑乙二醇

【题目描述】

\(2s,512Mb\)

鲁迅曾经说过,要多去尝试,才能最终发现最优的答案。

鲁迅也还说过,要珍惜当下,把握住眼前的机会。

\(n\)个随机数生成器,第 \(i\)个生成器可以均匀随机地生成 \([L_i,R_i]\)内的一个实数。

现在你要玩个游戏,从第 \(1\)个生成器到第\(n\) 个生成器,每次当前生成器会生成一个数,你需要选择:

  1. 相信鲁迅,拿走这个数,游戏结束。
  2. 相信鲁迅,放弃这个数和这个生成器,使用下一个生成器(前提是下一个生成器必须存在)。
    求使用使得期望答案最大的策略时,期望答案是多少。

【输入格式】

第一行一个整数\(n\)

接下来 \(n\)行每行两个非负整数表示\(L_i,R_i\)

【输出格式】

一行一个保留恰好 $5 $位小数(四舍五入)的浮点数表示答案。

【样例 \(1\) 输入】

2
0 2
1 1

【样例 \(1\) 输出】

1.25000

【样例 \(2\) 输入】

3
0 9
0 9
0 9

【样例 \(1\) 输出】

6.25781

【样例 \(1\) 解释】

如果第一个生成器生成了比 \(1\)小的数就使用下一个,否则拿走,答案的期望为\(5/4\)

【数据范围】

对于 \(30\%\) 的数据:\(L_i = R_i\)

对于另外 \(40\%\) 的数据:所有 \(L_i\) 都为 \(0\) 且所有 \(R_i\) 都相等。

对于所有数据:\(1\le n \le 10^6, 0 \le L_i \le R_i \le 10^9\)

【问题分析】

\(30\) 分:

\(L_i=R_i\) ,不存在概率选择,只选择最大的即可。

\(30\) 分程序】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000010;
int n,L[N],R[N];
int main()
{
	scanf("%d",&n);
	int flag=1,ans=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&L[i],&R[i]);
		if(L[i]!=R[i]) flag=0;
		ans=max(ans,L[i]);
	}
	if(flag) {printf("%.5lf",ans*1.0);return 0;}
	return 0;
} 

技术情报局

【题目描述】

\(3s,2048MB\)

有这样一道简单题:给定一个序列求所有区间的最大值的和。

还有这样一道简单题:给定一个序列求所有区间的乘积的和。

众所周知:简单题 + 简单题 = 简单题。

所以,给定一个长为\(n\) 的正整数序列,定义一个区间的权值是该区间的数的乘积乘上该区间的最大值,求该序列所有区间的权值和,答案对\(p\) 取模。

【输入格式】

为了避免输入文件过大,输入仅有一行五个正整数:\(n,s,l,r,p\)

然后通过调用如下函数 get(n, s, l, r) 得到一个长为\(n\) 的序列,序列的每个数都是\([l,r]\) 范围内的整数:

#include <vector>
namespace GenHelper {
unsigned z1, z2, z3, z4, b;
unsigned rand_() {
b = ((z1 << 6) ^ z1) >> 13;
z1 = ((z1 & 4294967294U) << 18) ^ b;
b = ((z2 << 2) ^ z2) >> 27;
z2 = ((z2 & 4294967288U) << 2) ^ b;
b = ((z3 << 13) ^ z3) >> 21;
z3 = ((z3 & 4294967280U) << 7) ^ b;
b = ((z4 << 3) ^ z4) >> 12;
z4 = ((z4 & 4294967168U) << 13) ^ b;
return (z1 ^ z2 ^ z3 ^ z4);
}
} // namespace GenHelper
std::vector<int> get (int n, unsigned s, int l, int r) {
std::vector<int> a;
using namespace GenHelper;
z1 = s;
z2 = unsigned((~s) ^ 0x233333333U);
z3 = unsigned(s ^ 0x1234598766U);
z4 = (~s) + 51;
for (int i = 1; i <= n; i ++) {
int x = rand_() & 32767;
int y = rand_() & 32767;
a.push_back(l + (x * 32768 + y) % (r - l + 1));
}
return a;
}

【输出格式】

一行一个正整数表示答案模 \(p\) 的结果。

【样例 \(1\) 输入】

3 233 1 5 1000

【样例 \(1\) 输出】

256

【样例 \(2\) 输入】

1000 2333 1 1000 998244353

【样例 \(2\) 输出】

627435628

【样例 \(3\) 输入】

10000000 666 1 1000 998244353

【样例 \(3\) 输出】

123415035

【样例 \(1\) 解释】

该样例的序列为 \({3,5,2}\)

【数据范围】

对于 \(20\%\) 的数据:\(n \le 500\)

对于 \(40\%\) 的数据:\(n \le 5000\)

对于 \(60\%\) 的数据:\(n \le 10^6\)

对于另外 \(20\%\) 的数据: \(l=r\)

对于所有数据:\(1 \le n \le 10^7, 0 \le s \le 2^{30}, 1 \le l \le r \le 10^9, 1 \le p \le 10^9+9\)

【问题分析】

\(20\) 分:

暴力枚举每一个区间,计算每一个区间的最大值和乘积。时间复杂度为 \(O(n^3)\)

\(40\) 分:

枚举每一个区间,用递推的方式求解区间最大值和乘积,已知区间 \([x,y]\) 的最大值 Max,区间乘积 \(S\),则区间\([x,y+1]\) 的最大值就是 \(max(Max,a[y+1])\) ,区间乘积为 \(S\times a[y+1]\) 。时间复杂度为 \(O(n^2)\)

\(60\) 分:

\(40\) 分的基础上,其中 \(l=r\) ,则区间所有数都是相同的,只需要枚举区间长度即可,区间最大值,区间乘积都可以 \(O(1)\) 得出,时间复杂度为 \(O(n)\)

肯德基

【题目描述】

\(3s,512MB\)

定义\(f(x) = \mu (x)^2x\) ,求:\(\sum_{i=1}^nf(i)\)

\(\mu\)是莫比乌斯函数,\(\mu(1)=1\)\(x>1\)\(\mu(x)\)有值当且仅当\(x\) 可以表示为 \(k\)个互不相等的质数的乘积,此时 \(\mu(x)\)的值为\((-1)^k\)

【输入格式】

多组数据,第一行一个整数\(T\) 表示数据组数。

接下来\(T\) 行每行一个整数\(n\)

【输出格式】

\(T\)行,每行表示该组数据的答案,鉴于答案可能很大,对\(2^{64}\) 取模 。

【样例 \(1\) 输入】

3
10
100
1000

【样例 \(1\) 输出】

34
2967
303076

【数据范围】

【问题分析】

\(20\) 分:

使用埃式筛分预处理出 \(f(x)\) 的前缀和,时间复杂度接近 \(O(n)\) 。对于每次询问,在 \(O(1)\) 的时间回答。

\(20\) 分参考程序】

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=50010;
int n,T,mob[N],v[N];//定义数组mob[N]保存莫比乌斯函数,v[N]标记是否为质数
ull sum[N];
int main()
{

	for(int i=1;i<=N;i++)	//埃式筛法求1~N的莫比乌斯函数 
    	mob[i]=1,v[i]=0;
	for(int i=2;i<=N;i++)		
	{	
    	if(v[i]) continue;		//被质数筛过,为合数
    	mob[i]=-1;				//i为质数
   		for(int j=2*i;j<=N;j+=i)
   		{
        	v[j]=i;
        	if((j/i)%i==0) mob[j]=0;
        	else mob[j]=mob[j]*-1;
    	}
	}		
	for(int i=1;i<=N;i++)
		sum[i]=sum[i-1]+mob[i]*mob[i]*i;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		printf("%lld\n",sum[n]);
	}
	return 0;
} 
posted @ 2020-10-11 16:36  _BOSS  阅读(245)  评论(0)    收藏  举报