清北澡堂模拟一

题解 清北澡堂模拟一

0.1 前言

第一场模拟,可以说是尽可能地发挥了吧。

”如何证明你爱OI爱得深沉?"

“我愿意为了OI而去学数学”

1.1 宝箱 题目

宝箱

【题目】

小L有一个宝箱,其中有很多宝物。

经过统计,小L发现宝箱中一共有n种宝物,第\(i\)种有\(a_i\)种,同种宝物完全等价。

由于宝箱上铭刻有一个魔咒,所以小L取宝物的顺序需要满足一个条件:

小L必须先取完第\(i-1\)种宝物才能把第\(i\)种宝物取光。

如果不满足这个条件,宝就会爆炸,之前取的所有宝也都会消失。

那么问题来了,小L有多少种不同的取宝物方案呢?

【输入】

第一行一个整数\(n\)表示宝物的种类数。

之后一行\(n\)个数,第\(i\)个表示第\(i\)种宝物有\(a_i\)种。

【输出】

一个整数\(ans\),\(ans\)\(1000000007\)

【样例一输入】

3
1 2 2

【样例一输出】

8

【样例一解释】

第一种宝物用A表示,第二种宝用B表示,第三种宝物用C表示。

那么合法序列有ABBCC、ABCBC、ACBBC、BABCC、BACBC、BCABC、CABBC、CBABC

对于\(100%\)的数据,\(n\leq50000\),\(\sum a_i \leq 1000000\) , $ a_i \geq 1 $

1.2 题解

虽然第一眼看上去不是很有思路,但冷静分析一下还是有突破口的。

首先观察一下样例,会发现如下事实:最后结尾必定是\(C\)。这只是一个巧合吗?

事实上这是必然的,口胡一下证明:当C放到最后一个时,B必定已经放完了;而要想B放完,又要求A已经放完;因此C放完之时,A,B均已经放完了,最后一个C只能放在最后一个位置

此时还剩下:\(A\times 1\),\(B\times 2\),\(C\times 1\),而这个\(C\)放在任何位置都是无所谓的,没有任何条件可以限制到它;排列好\(A\)\(B\)之后,带上\(C\)的总方案数是:\(num\times C_4^1\)

到这里,我们已经完全排除了来自\(C\)的干扰,总方案数变得只与\(A\)\(B\)有关。而处理方式类似:固定住最后一个\(B\),递归处理\(A\)之后\(\times C_2^1\)

其实思路已经在叙述中体现出来了:设\(dp[i]\)表示仅有\([1..i]\)种宝箱时的方案数,设\(sum[i]\)表示\([1..i]\)种宝箱的个数前缀和,设\(num[i]\)表示第\(i\)种宝箱的数目。则转移方程如下:

\[dp[i]=dp[i-1]\times C_{sum[i]-1}^{num[i]-1} \]

考场上顺利\(AC\)^-^

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define lor(a,b,c) for(register int a=b;a<=c;++a)
#define ror(a,b,c) for(register int a=c;a>=b;--a)

const int MOD=1000000007,MAX=1e6+5,TOP=1e6;

int n; ll ans;
int liyo[MAX],sum[MAX];
ll tim[MAX],inv[MAX];

inline int read();
inline void init();
inline ll qsm(ll,int);
inline ll compose(int,int);

int main(){
	freopen("treasure.in","r",stdin);
	freopen("treasure.out","w",stdout);
	
	n=read(); lor(i,1,n) liyo[i]=read(),sum[i]=sum[i-1]+liyo[i];
	init();
	
	ans=1;
	lor(i,2,n){
		(ans*=compose(sum[i-1]+liyo[i]-1,liyo[i]-1))%=MOD;
	}
	cout<<ans<<endl;
	
	return 0;	
}

inline int read(){
	char tmp=getchar(); int sum=0; bool flag=false;
	while(tmp<'0'||tmp>'9'){
		if(tmp=='-') flag=true;
		tmp=getchar();
	}
	while(tmp>='0'&&tmp<='9'){
		sum=(sum<<1)+(sum<<3)+tmp-'0';
		tmp=getchar();	
	}
	return flag?-sum:sum;
}

inline void init(){
	tim[0]=inv[0]=1;
	tim[1]=1;
	lor(i,2,TOP) tim[i]=1ll*tim[i-1]*i%MOD;
	inv[TOP]=qsm(tim[TOP],MOD-2);
	ror(i,1,TOP-1) inv[i]=1ll*inv[i+1]*(i+1)%MOD;
}

inline ll qsm(ll a,int k){
	ll ans=1,base=a;
	while(k){
		if(k&1) (ans*=base)%=MOD;
		(base*=base)%=MOD;
		k>>=1;
	}
	return ans;
}

inline ll compose(int a,int b){
	ll ans=((((tim[a]*inv[b])%MOD)*inv[a-b])%MOD);
	return ans%MOD;	
}

2.1 平均数 题目

我们一般计算n个数的平均数时,会用

\[\overline x = \frac{x_1+x_2+..+x_n}{n} \]

的方法,然而由于储存数值大小的限制,当这些数的和超过\(2^{31}-1\)时,计算就会出现问题。
所以这里我们找到了一种新方法:
当计算\(x_1\),\(x_2\),\(x_3\),\(x_4\),\(x_5\),\(x_6\)的平均值时,我们可以先计算出

\[\overline x_1 = \frac{x_1+x_2+x_3}{3} \]

\[\overline x_2 = \frac{x_4+x_5+x_6}{3} \]

再利用\(\overline x_1\)\(\overline x_2\)计算出

\[\overline{x} = \frac{\overline{x_1}+\overline{x_2}}{2} \]

通过这种方式,我们只要保证每个数都不超过\(\lfloor \frac{2^{31}-1}{3} \rfloor = 715827882\)

现在给定\(n\),请问通过这种分治求平均数的方式,保证每个数不超过多少可以求出\(n\)个数的平均数。

第一行一个整数\(T\)表示数据组数。
之后每一组数据\(n\),表示求\(n\)个数的平均数。

对于每组数据输出一行,一个数\(ans\),表示只要保证\(n\)个数都不超过\(ans\)就能正确求出来均数。

对于 \(100%\)的数据,\(n\leq 10^9,T\leq 5000\)

【输入】

5

6

97

120

65536

2147483647

【输出】

715827882

22139006

429496729

1073741823

1

2.2 题解

题目说的很拽,其是就是分解最大质因子的意思(っ °Д °;)っ

一提到“分解最大质因子”,马上想到了\(Pollard \ P\),但是被出题人卡掉了..(#`-_ゝ-)

虽然出题人的解释是\(Pollard \ P\)天然大常数,但有人$Pollard \ P $就过了啊...( _ _)ノ|

正解总结其来其实就是"取\(n\)的最大质因子技巧":

\(n\)有一个大于\(\sqrt n\)的质因子\(x\),可以保证只有一个符合条件的\(x\),且其指数的系数为\(1\)。因此我们只需要筛出所有\(\leq \sqrt n\)的质因数,最后特判一下是否在类似的\(x\)即可。

关键部分的代码,重点强调:

inline int calc(int n){
	int ans=1;
	for(int i=1;i<=prime[0]&&prime[i]*prime[i]<=n;++i){ //时刻注意着只筛到根号
		if(n%prime[i]==0) ans=prime[i];
		while(n%prime[i]==0) n/=prime[i]; //消去这个质因子
	}
	ans=max(ans,n); //特判
	return ans;
}

完整版:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define lor(a,b,c) for(register int a=b;a<=c;++a)
#define ror(a,b,c) for(register int a=c;a>=b;--a)

const int MAX=1e9+5,SQRT=4e5+5,MAXINT=2147483647;

bool vis[SQRT+10];
int prime[SQRT+10];
int T,n;

void init();
inline int read();
inline int calc(int);

int main(){
	#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
//	freopen("test.out","w",stdout);
	#endif
	
	init();
	
	T=read();
	while(T--){
		printf("%d\n",MAXINT/calc(read()));
	}
	
	return 0;
}

void init(){
	lor(i,2,SQRT){
		if(!vis[i]) prime[++prime[0]]=i;
		for(int j=1;j<=prime[0]&&i*prime[j]<=SQRT;++j){
			vis[i*prime[j]]=true;
			if(i%prime[j]==0) break;	
		}
	}
}

inline int read(){
	char tmp=getchar(); int sum=0; bool flag=false;
	while(tmp<'0'||tmp>'9'){
		if(tmp=='-') flag=true;
		tmp=getchar();
	}
	while(tmp>='0'&&tmp<='9'){
		sum=(sum<<1)+(sum<<3)+tmp-'0';
		tmp=getchar();	
	}
	return flag?-sum:sum;
}

inline int calc(int n){
	int ans=1;
	for(int i=1;i<=prime[0]&&prime[i]*prime[i]<=n;++i){
		if(n%prime[i]==0) ans=prime[i];
		while(n%prime[i]==0) n/=prime[i];
	}
	ans=max(ans,n);
	return ans;
}

诶,太大意了,以为能稳\(AC\),然后斯望\(230\)来着╯︿╰

3.1 密码 题目

给定\(s\),\(p\)解方程:

\[t^{2^{30}+3} \equiv s (\mod p) \]

保证\(2\leq p \leq 10^9\),\(T\leq 10^5\)

【输入】

5

319 1499

9731 29683

8061 74707

64766 94207

34922 58967

【输出】

449

19038

26936

29213

53097

3.2 密码 题解

这题......既简单,又难的一匹(#°Д°)

简单在这个方程是个会算数的人都能推出来。喏,就是这个嘛:

\[t^{s^{30}+3}\equiv s (mod\ \ p) \]

但是...难在...我还真就盯着方程没思路,灰溜溜地打了\(30pts\)的暴力分...

那就解方程呗:

\[t^{s^{30}+3}\equiv s (mod\ \ p) \]

\[t^{s^{30}+3 \mod \phi(p)}\equiv s (mod\ \ p) \]

\[t\equiv s^{\frac{1}{s^{30}+3} \mod \phi(p)} (mod\ \ p) \]

由于\(p\)是质数,\(\phi(p)\)其实就是\(p-1\)\({s^{30}+3} \mod \phi(p)\)其就是求\(s^{30}+3\)在模\(p-1\)意下的逆元,记为\(m'\)

\(t\equiv s^{m'} (\mod p)\)

哦,注意一下,虽然\(p\)是质数,但不代表\(\phi(p)\)(对于这道题来说就不是),因此求逆原不能偷懒用费马小定理,而应当用\(ex \ gcd\)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define lor(a,b,c) for(register int a=b;a<=c;++a)
#define ror(a,b,c) for(register int a=c;a>=b;--a)

const ll REC=1073741824;

int t,s,p;

inline int read();
ll qsm(ll,ll,ll);
ll exgcd(ll,ll,ll&,ll&);
ll get_inv(ll,ll);

int main(){
	#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
	#endif
	
	t=read();
	while(t--){
		scanf("%lld%lld",&s,&p);
		ll d=REC+3,md=p-1;
		printf("%lld\n",qsm(s,get_inv(d,md),p));
	}
	
	return 0;	
}

inline int read(){
	char tmp=getchar(); int sum=0; bool flag=false;
	while(tmp<'0'||tmp>'9'){
		if(tmp=='-') flag=true;
		tmp=getchar();
	}
	while(tmp>='0'&&tmp<='9'){
		sum=(sum<<1)+(sum<<3)+tmp-'0';
		tmp=getchar();	
	}
	return flag?-sum:sum;
}

ll qsm(ll a,ll k,ll mod){
	ll ans=1,base=a;
	while(k){
		if(k&1) (ans*=base)%=mod;
		(base*=base)%=mod;
		k>>=1;
	}
	return ans;
}

ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b) {x=1; y=0; return a;}
	ll d=exgcd(b,a%b,y,x);
	y=y-(a/b)*x;	
	return d;
}

ll get_inv(ll a,ll mod){
	ll inv,tmp; exgcd(a,mod,inv,tmp);
	((inv%=mod)+=mod)%mod;
	return inv;	
}

4.1 总结

没什么好总结的,再引用一下名言吧

”如何证明你爱OI爱得深沉?"

“我愿意为了OI而去学数学”

猜你喜欢

转载自www.cnblogs.com/ticmis/p/13210976.html