良心的noip模拟赛
首先感谢队爷Achen学长为我们出的一套"良心的noip模拟题"(原话如此)。
虽说他总是自称蒟(Amorphophalms\tiny morphophalmsmorphophalms)蒻(konjac\tiny onjaconjac),但是这改变不了他总是AK的事实。
确实是一套良(du)心(liu)的题目。
T1T1T1
题面如此。
数位Dp可能是第一想法,但是位数高达10510^5105级别的数位Dp确实不太科学。
针对题目的要求,发现可以使用并查集来维护不同位之间的关系。
考虑直接枚举原数的每一个前缀并且接下来一个数比原来小,这种前提下的方案数就是剩下位置的任意选择。
显然,选择方案就是10之前还没有被限制块的个数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;
}
这道题目确实可做,其中奇怪的枚举前缀的思路是非常有趣的。
T2T2T2
“这是一道披着期望皮的容斥题”——Achen
发现n的范围巨大,由于m也不小所以矩阵快速幂宣告GG。
所以直觉(数据范围)告诉我们一定会有与m有关的多项式算法。
先感受一下简单的O(m2)O(m^2)O(m2)算法。
首先,期望可以由概率*贡献累加得到。
考虑使第i个点成为最小的白球。
其充要条件是:
1、第i个球是白球
2、前i-1个球全部是黑球
第i个球是白色的概率很好算,(m−1m)n\left ( \frac{m-1}{m} \right )^n(mm−1)n。
但是这时候前i-1个球颜色是随意的。
考虑容斥,先减掉至少有一个白球的情况数:C(i−1,1)∗(m−2m)nC(i-1,1)*\left ( \frac{m-2}{m} \right )^nC(i−1,1)∗(mm−2)n。
显然要再加上至少有两个白球的情况数:C(i−1,2)∗(m−3m)nC(i-1,2)*\left ( \frac{m-3}{m} \right )^nC(i−1,2)∗(mm−3)n
最后算出答案还要加上当前点的贡献iii
最后公式大概是:∑i=1m+1∑j=0i−1(−1)j(i−1j)(m−j−1m)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)^ni=1∑m+1j=0∑i−1(−1)j(ji−1)(mm−j−1)n
乘上答案的mnm^nmn就是:Ans=∑i=1m+1∑j=0i−1(−1)j(i−1j)(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)^nAns=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;
大概长这样?
505050滚粗。
我们来看一下优秀而的满分算法。
事实上只是状态的定义有所不同。
(以下期望都是在已经乘上mnm^nmn的前提下进行推导的)
首先令f(x)f(x)f(x)为xxx是最小白球编号的期望。
那么上式可化简为Ans=∑i=1m+1i∗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)为最小白球编号大于xxx的期望,也就是说前xxx个球全部为黑球的期望。
计算贡献,有Ans=∑i=1m+1i∗f(i)⇒Ans=∑i=1m+1∑j=1if(i)⇒Ans=∑i=0mp(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)
按照505050分的算法展开p(i)p(i)p(i)
p(x)=∑i=0x(−1)i(xi)(m−i)n\displaystyle \large p(x)=\sum_{i=0}^x(-1)^i\binom {x}{i}\left(m-i\right)^np(x)=i=0∑x(−1)i(ix)(m−i)n
⟹Ans=∑i=0m∑k=0i(−1)k(ik)(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(m2)O(m^2)O(m2)的,但是我们可以改变枚举顺序:Ans=∑k=0m(−1)k(m−k)n(∑i=km(ik))\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))
后面那一坨可以化成一个组合数
Ans=∑k=0m(−1)k(m−k)n(m+1k+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(mlogm)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;
}
T3T3T3
emmm
这题NOIpNOIpNOIp结束之后再看吧
听说要用Min_25筛。。。