题解 清北澡堂模拟一
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\)种宝箱的数目。则转移方程如下:
考场上顺利\(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个数的平均数时,会用
的方法,然而由于储存数值大小的限制,当这些数的和超过\(2^{31}-1\)时,计算就会出现问题。
所以这里我们找到了一种新方法:
当计算\(x_1\),\(x_2\),\(x_3\),\(x_4\),\(x_5\),\(x_6\)的平均值时,我们可以先计算出
和
再利用\(\overline x_1\),\(\overline x_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\)解方程:
保证\(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 密码 题解
这题......既简单,又难的一匹(#°Д°)
简单在这个方程是个会算数的人都能推出来。喏,就是这个嘛:
但是...难在...我还真就盯着方程没思路,灰溜溜地打了\(30pts\)的暴力分...
那就解方程呗:
由于\(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而去学数学”