良心的noip模拟赛
首先感谢队爷Achen学长为我们出的一套"良心的noip模拟题"(原话如此)。
虽说他总是自称蒟(A)蒻(k),但是这改变不了他总是AK的事实。
确实是一套良(du)心(liu)的题目。
题面如此。
数位Dp可能是第一想法,但是位数高达级别的数位Dp确实不太科学。
针对题目的要求,发现可以使用并查集来维护不同位之间的关系。
考虑直接枚举原数的每一个前缀并且接下来一个数比原来小,这种前提下的方案数就是剩下位置的任意选择。
显然,选择方案就是。
如果发现在满足当前前缀的前提下不能满足之后位数的限制,就结束循环。
#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;
}
这道题目确实可做,其中奇怪的枚举前缀的思路是非常有趣的。
“这是一道披着期望皮的容斥题”——Achen
发现n的范围巨大,由于m也不小所以矩阵快速幂宣告GG。
所以直觉(数据范围)告诉我们一定会有与m有关的多项式算法。
先感受一下简单的算法。
首先,期望可以由概率*贡献累加得到。
考虑使第i个点成为最小的白球。
其充要条件是:
1、第i个球是白球
2、前i-1个球全部是黑球
第i个球是白色的概率很好算,。
但是这时候前i-1个球颜色是随意的。
考虑容斥,先减掉至少有一个白球的情况数:。
显然要再加上至少有两个白球的情况数:
最后算出答案还要加上当前点的贡献
最后公式大概是:
乘上答案的就是:
考场上就是写的这个玩意儿。
发现并不会化简
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;
大概长这样?
滚粗。
我们来看一下优秀而的满分算法。
事实上只是状态的定义有所不同。
(以下期望都是在已经乘上的前提下进行推导的)
首先令为是最小白球编号的期望。
那么上式可化简为
令为最小白球编号大于的期望,也就是说前个球全部为黑球的期望。
计算贡献,有
按照分的算法展开
这个式子仍然是的,但是我们可以改变枚举顺序:
后面那一坨可以化成一个组合数
然后使用快速幂就是优秀算法
(其实还可以用线性筛代替快速幂做到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;
}
emmm
这题结束之后再看吧
听说要用Min_25筛。。。