良心的noip模拟赛
首先感谢队爷Achen学长为我们出的一套"良心的noip模拟题"(原话如此)。
虽说他总是自称蒟(A
m
o
r
p
h
o
p
h
a
l
m
s
\tiny morphophalms
morphophalms)蒻(k
o
n
j
a
c
\tiny onjac
onjac),但是这改变不了他总是AK的事实。
确实是一套良(du)心(liu)的题目。
T 1 T1 T1
题面如此。
数位Dp可能是第一想法,但是位数高达
1
0
5
10^5
105级别的数位Dp确实不太科学。
针对题目的要求,发现可以使用并查集来维护不同位之间的关系。
考虑直接枚举原数的每一个前缀并且接下来一个数比原来小,这种前提下的方案数就是剩下位置的任意选择。
显然,选择方案就是
1
0
之
前
还
没
有
被
限
制
块
的
个
数
10^{\tiny 之前还没有被限制块的个数}
10之前还没有被限制块的个数。
如果发现在满足当前前缀的前提下不能满足之后位数的限制,就结束循环。
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=100005;
const int mod=19260817;
inline int read() {
char c; int rec=0;
while((c=getchar())<'0'||c>'9');
while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
return rec;
}
int n,m,a[Maxn],b[Maxn],fa[Maxn];
inline int getfa(int x) {return x==fa[x]?x:fa[x]=getfa(fa[x]);}
inline int Ksm(int a,int x) {
int rec=1; while(x) { if(x&1) rec=1ll*rec*a%mod; a=1ll*a*a%mod; x>>=1;} return rec;
}
inline int Sov(int *x) {
int rec=0,cnt=0;
for(int i=1;i<=n;++i) if(fa[i]==i) ++cnt;
for(int i=n;i;--i) {
if(fa[i]==i) rec=(rec+x[i]*Ksm(10,--cnt))%mod;
if(x[getfa(i)]<x[i]) rec=(rec+Ksm(10,cnt))%mod;
if(x[getfa(i)]!=x[i]) break;
} return rec;
}
int main() {
n=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) b[i]=read();
for(int i=1;i<=n;++i) fa[i]=i;
m=read();
for(int i=1;i<=m;++i) {
int x=getfa(read()),y=getfa(read());
fa[min(x,y)]=max(x,y);
}
cout<<(Sov(b)-Sov(a)+mod)%mod;
return 0;
}
这道题目确实可做,其中奇怪的枚举前缀的思路是非常有趣的。
T 2 T2 T2
“这是一道披着期望皮的容斥题”——Achen
发现n的范围巨大,由于m也不小所以矩阵快速幂宣告GG。
所以直觉(数据范围)告诉我们一定会有与m有关的多项式算法。
先感受一下简单的
O
(
m
2
)
O(m^2)
O(m2)算法。
首先,期望可以由概率*贡献累加得到。
考虑使第i个点成为最小的白球。
其充要条件是:
1、第i个球是白球
2、前i-1个球全部是黑球
第i个球是白色的概率很好算,
(
m
−
1
m
)
n
\left ( \frac{m-1}{m} \right )^n
(mm−1)n。
但是这时候前i-1个球颜色是随意的。
考虑容斥,先减掉至少有一个白球的情况数:
C
(
i
−
1
,
1
)
∗
(
m
−
2
m
)
n
C(i-1,1)*\left ( \frac{m-2}{m} \right )^n
C(i−1,1)∗(mm−2)n。
显然要再加上至少有两个白球的情况数:
C
(
i
−
1
,
2
)
∗
(
m
−
3
m
)
n
C(i-1,2)*\left ( \frac{m-3}{m} \right )^n
C(i−1,2)∗(mm−3)n
最后算出答案还要加上当前点的贡献
i
i
i
最后公式大概是:
∑
i
=
1
m
+
1
∑
j
=
0
i
−
1
(
−
1
)
j
(
i
−
1
j
)
(
m
−
j
−
1
m
)
n
\displaystyle\sum_{i=1}^{m+1}\sum_{j=0}^{i-1} (-1)^j\binom{i-1}{j}\left(\frac{m-j-1}{m}\right)^n
i=1∑m+1j=0∑i−1(−1)j(ji−1)(mm−j−1)n
乘上答案的
m
n
m^n
mn就是:
A
n
s
=
∑
i
=
1
m
+
1
∑
j
=
0
i
−
1
(
−
1
)
j
(
i
−
1
j
)
(
m
−
j
−
1
)
n
\displaystyle Ans=\sum_{i=1}^{m+1}\sum_{j=0}^{i-1} (-1)^j\binom{i-1}{j}\left(m-j-1\right)^n
Ans=i=1∑m+1j=0∑i−1(−1)j(ji−1)(m−j−1)n
考场上就是写的这个玩意儿。
发现并不会化简
for(long long i=1;i<=m;++i) {
long long temp=Ksm(m-1,n);
for(long long j=1,f=-1;j<i;++j,f=-f) {
temp+=f*C(i-1,j)*Ksm(m-j-1,n)%mod;
temp=(temp+mod)%mod;
}
temp=i*temp%mod;
ans=(ans+temp)%mod;
}
long long temp=Ksm(m,n);
for(long long j=1,f=-1;j<=m;++j,f=-f) {
temp+=1*f*C(m,j)*Ksm(m-j,n)%mod;
temp=(temp+mod)%mod;
}
temp=(m+1)*temp%mod;
ans=(ans+temp)%mod;
大概长这样?
50
50
50滚粗。
我们来看一下优秀而的满分算法。
事实上只是状态的定义有所不同。
(以下期望都是在已经乘上
m
n
m^n
mn的前提下进行推导的)
首先令
f
(
x
)
f(x)
f(x)为
x
x
x是最小白球编号的期望。
那么上式可化简为
A
n
s
=
∑
i
=
1
m
+
1
i
∗
f
(
i
)
\displaystyle Ans=\sum_{i=1}^{m+1}i*f(i)
Ans=i=1∑m+1i∗f(i)
令
p
(
x
)
p(x)
p(x)为最小白球编号大于
x
x
x的期望,也就是说前
x
x
x个球全部为黑球的期望。
计算贡献,有
A
n
s
=
∑
i
=
1
m
+
1
i
∗
f
(
i
)
⇒
A
n
s
=
∑
i
=
1
m
+
1
∑
j
=
1
i
f
(
i
)
⇒
A
n
s
=
∑
i
=
0
m
p
(
i
)
\displaystyle Ans=\sum_{i=1}^{m+1}i*f(i)\Rightarrow Ans=\sum_{i=1}^{m+1}\sum_{j=1}^{i}f(i)\Rightarrow Ans=\sum_{i=0}^{m}p(i)
Ans=i=1∑m+1i∗f(i)⇒Ans=i=1∑m+1j=1∑if(i)⇒Ans=i=0∑mp(i)
按照
50
50
50分的算法展开
p
(
i
)
p(i)
p(i)
p
(
x
)
=
∑
i
=
0
x
(
−
1
)
i
(
x
i
)
(
m
−
i
)
n
\displaystyle \large p(x)=\sum_{i=0}^x(-1)^i\binom {x}{i}\left(m-i\right)^n
p(x)=i=0∑x(−1)i(ix)(m−i)n
⟹
A
n
s
=
∑
i
=
0
m
∑
k
=
0
i
(
−
1
)
k
(
i
k
)
(
m
−
k
)
n
\displaystyle \Longrightarrow Ans=\sum_{i=0}^m\sum_{k=0}^i(-1)^k\binom{i}{k}\left(m-k\right)^n
⟹Ans=i=0∑mk=0∑i(−1)k(ki)(m−k)n
这个式子仍然是
O
(
m
2
)
O(m^2)
O(m2)的,但是我们可以改变枚举顺序:
A
n
s
=
∑
k
=
0
m
(
−
1
)
k
(
m
−
k
)
n
(
∑
i
=
k
m
(
i
k
)
)
\displaystyle Ans=\sum_{k=0}^m(-1)^k(m-k)^n\left(\sum_{i=k}^m\binom{i}{k}\right)
Ans=k=0∑m(−1)k(m−k)n(i=k∑m(ki))
后面那一坨可以化成一个组合数
A
n
s
=
∑
k
=
0
m
(
−
1
)
k
(
m
−
k
)
n
(
m
+
1
k
+
1
)
\displaystyle Ans=\sum_{k=0}^m(-1)^k(m-k)^n\binom{m+1}{k+1}
Ans=k=0∑m(−1)k(m−k)n(k+1m+1)
然后使用快速幂就是
O
(
m
l
o
g
m
)
O(mlogm)
O(mlogm)优秀算法
(其实还可以用线性筛代替快速幂做到O(m))。
代码极简
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,m,ans=0;
const int mod=1e9+7;
inline int Ksm(int a,int x) {
int rec=1ll;
while(x) {
if(x&1ll) rec=1ll*rec*a%mod;
a=1ll*a*a%mod; x>>=1ll;
} return rec%mod;
}
int fac[1000005],inv[1000005];
inline int C(int n,int m) {
return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main() {
cin>>n>>m;
fac[0]=inv[0]=1;
for(int i=1;i<=m+1;++i) fac[i]=1ll*fac[i-1]*i%mod;
inv[m+1]=Ksm(fac[m+1],mod-2);
for(int i=m;i;--i) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=0,f=1;i<=m;++i,f=-f) {
int temp=f*(1ll*Ksm(m-i,n)*C(m+1,i+1)%mod);
ans=(ans+temp)%mod;
}
ans=(ans+mod)%mod;
cout<<ans;
return 0;
}
T 3 T3 T3
emmm
这题
N
O
I
p
NOIp
NOIp结束之后再看吧
听说要用Min_25筛。。。