2019/11/06 校内模拟

今天的 CSP-S 模拟比 NOI 模拟难。。

CSP-S 模拟

T1 轻飘飘的时间

为了方便,我们记 g ( n ) = ( n B ) g(n)=\binom n B g(n)=(Bn)

题目要我们求的是:

a n s = ∑ i = 1 n ∑ j = 1 m g ( gcd ⁡ ( i , j ) ) ans=\sum_{i=1}^n\sum_{j=1}^mg(\gcd(i,j)) ans=i=1nj=1mg(gcd(i,j))

那么,先按照莫反的套路化简一波(令 u p = min ⁡ ( n , m ) up=\min(n,m) up=min(n,m)):

a n s = ∑ k = 1 u p g ( k ) ∑ i = 1 ⌊ n k ⌋ ∑ j = 1 ⌊ m k ⌋ [ gcd ⁡ ( i , j ) = 1 ] = ∑ k = 1 u p g ( k ) ∑ i = 1 ⌊ n k ⌋ ∑ j = 1 ⌊ m k ⌋ ∑ d ∣ i , d ∣ j μ ( d ) = ∑ k = 1 u p g ( k ) ∑ d = 1 ⌊ u p k ⌋ μ ( d ) ⌊ n k d ⌋ ⌊ m k d ⌋ \begin{aligned} ans&=\sum_{k=1}^{up}g(k)\sum_{i=1}^{\lfloor\frac n k\rfloor}\sum_{j=1}^{\lfloor\frac m k\rfloor}[\gcd(i,j)=1]\\ &=\sum_{k=1}^{up}g(k)\sum_{i=1}^{\lfloor\frac n k\rfloor}\sum_{j=1}^{\lfloor\frac m k\rfloor}\sum_{d|i,d|j}\mu(d)\\ &=\sum_{k=1}^{up}g(k)\sum_{d=1}^{\lfloor\frac{up}k\rfloor}\mu(d)\lfloor\frac n {kd}\rfloor\lfloor\frac m {kd}\rfloor \end{aligned} ans=k=1upg(k)i=1knj=1km[gcd(i,j)=1]=k=1upg(k)i=1knj=1kmdi,djμ(d)=k=1upg(k)d=1kupμ(d)kdnkdm

T = k d T=kd T=kd,则:

a n s = ∑ k = 1 u p ( ∑ k ∣ T g ( k ) μ ( T k ) ) ⌊ n T ⌋ ⌊ m T ⌋ ans=\sum_{k=1}^{up}\left(\sum_{k|T}g(k)\mu(\frac T k)\right)\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor ans=k=1upkTg(k)μ(kT)TnTm

f ( n ) = ∑ k ∣ n g ( k ) μ ( n k ) f(n)=\sum_{k|n}g(k)\mu(\frac n k) f(n)=kng(k)μ(kn),也即 f = g ∗ μ f=g*\mu f=gμ

由于在整除分块的时候,我们需要知道一段区间 f f f 值之和,那么不妨考虑算 f f f 的前缀和。

然后 n ≤ 1 0 10 n\le10^{10} n1010,自然能想到杜教筛。由于 f ∗ 1 = g ∗ μ ∗ 1 = g ∗ ϵ = g f*1=g*\mu*1=g*\epsilon=g f1=gμ1=gϵ=g,所以(记 S S S f f f 的前缀和):

∑ i = 1 n ∑ d ∣ i f ( i d ) = ∑ i = 1 n g ( i ) ∑ d = 1 n ∑ i = 1 ⌊ n d ⌋ f ( i ) = ∑ i = 1 n g ( i ) ∑ d = 1 n S ( ⌊ n d ⌋ ) = ∑ i = 1 n g ( i ) S ( n ) = ∑ i = 1 n g ( i ) − ∑ d = 2 n S ( ⌊ n d ⌋ ) \begin{aligned} \sum_{i=1}^n\sum_{d|i}f(\frac i d)&=\sum_{i=1}^ng(i)\\ \sum_{d=1}^n\sum_{i=1}^{\lfloor\frac n d\rfloor}f(i)&=\sum_{i=1}^ng(i)\\ \sum_{d=1}^nS(\lfloor\frac n d\rfloor)&=\sum_{i=1}^ng(i)\\ S(n)&=\sum_{i=1}^ng(i)-\sum_{d=2}^nS(\lfloor\frac n d\rfloor) \end{aligned} i=1ndif(di)d=1ni=1dnf(i)d=1nS(dn)S(n)=i=1ng(i)=i=1ng(i)=i=1ng(i)=i=1ng(i)d=2nS(dn)

然后就是要快速求 g g g 的前缀和了,组合数有一个性质,即 ∑ i = 1 n ( i m ) = ( n + 1 m + 1 ) \sum_{i=1}^n\binom im=\binom{n+1}{m+1} i=1n(mi)=(m+1n+1)(证明可以参考这里)。

又由于模数小,用 Lucas 定理求即可。

然后时间复杂度据 zxyoi 说是 O ( n 3 4 ) O(n^{\frac34}) O(n43) 的,可以用积分估计。

#include
using namespace std;
typedef long long ll;
const int N=3e6+5,P=9990017,inv2=4995009;
int B,ans,fac[P],ifac[P];
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return 1ll*x*y>=P?1ll*x*y%P:x*y;}
int power(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=mul(a,a))  if(b&1)  ans=mul(ans,a);
	return ans;
}
int C(int n,int m)  {return n<m?0:mul(fac[n],mul(ifac[m],ifac[n-m]));}
int Lucas(ll n,int m){
	if(n<P&&m<P)  return C(n,m);
	return mul(Lucas(n/P,m/P),C(n%P,m%P));
}
void prework(){
	fac[0]=fac[1]=1;
	for(int i=2;i<P;++i)  fac[i]=mul(fac[i-1],i);
	ifac[P-1]=power(fac[P-1],P-2);
	for(int i=P-2;~i;--i)  ifac[i]=mul(ifac[i+1],i+1);
}
int sum,prime[N],mark[N],mu[N],f[N];
void linear_sieves(){
	mu[1]=1;
	for(int i=2;i<N;++i){
		if(!mark[i])  prime[++sum]=i,mu[i]=P-1;
		for(int j=1;j<=sum&&i*prime[j]<N;++j){
			mark[i*prime[j]]=1;
			if(i%prime[j])  mu[i*prime[j]]=P-mu[i];
			else  {mu[i*prime[j]]=0;break;}
		}
	}
	for(int i=1;i<N;++i)
		for(int j=1;i*j<N;++j)  f[i*j]=add(f[i*j],mul(C(i,B),mu[j]));
	for(int i=1;i<N;++i)  f[i]=add(f[i],f[i-1]);
}
unordered_map<ll,int>Sum;
int S(ll n){
	if(n<N)  return f[n];
	if(Sum.count(n))  return Sum[n];
	int ans=Lucas(n+1,B+1);
	for(ll i=2,j;i<=n;i=j+1){
		j=n/(n/i);
		ans=dec(ans,mul((j-i+1)%P,S(n/i)));
	}
	return Sum[n]=ans;
}
ll n,m,up;
int main(){
	scanf("%lld%lld%d",&n,&m,&B);
	prework(),linear_sieves(),up=min(n,m);
	for(ll i=1,j;i<=up;i=j+1){
		j=min(n/(n/i),m/(m/i));
		ans=add(ans,mul(dec(S(j),S(i-1)),mul((n/i)%P,(m/i)%P)));
	}
	printf("%d\n",ans);
	return 0;
}

T2 我的订书机之恋

这道题的建图思路非常妙。

对于一个右端点,找到最近的左端点使得他们构成一个合法区间,并且定义他的父亲为最近左端点左边的点。

那么我们会建出一个森林,那么这个树形结构有什么性质呢。

考虑一个合法区间,他要么是一个极小合法区间,要么是多个合法区间的并。那在这棵树上,该右端点到根路径上的点都可以是合法左端点,那么他的答案就是他的深度

那么如果有两个右端点,那就是要找他们的合法区间交集的数量,求个 lca 即可。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include
using namespace std;
namespace IO{
	const int Rlen=1<<22|1;
	char buf[Rlen],*p1,*p2;
	inline char gc(){
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>
	inline T Read(){
		char c=gc();T x=0,f=1;
		while(!isdigit(c))  f^=(c=='-'),c=gc();
		while( isdigit(c))  x=(x+(x<<2)<<1)+(c^48),c=gc();
		return f?x:-x;
	}
	inline int in()  {return Read<int>();}
}
using IO::in;
const int N=2e6+5;
int n,m;
int p[N],L[N],R[N],rt[N],dep[N],fa[N][21],father[N];
stack<int>stk;
vector<int>e[N];
void Max(int &x,int y)  {if(x<y)x=y;}
void Min(int &x,int y)  {if(x>y)x=y;}
void dfs(int x,int root){
	rt[x]=root;
	for(int i=1;i<=20;++i)  fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int &to:e[x]){
		dep[to]=dep[x]+1,fa[to][0]=father[to];
		dfs(to,root);
	}
}
void build(){
	for(int i=1;i<=(n<<1);++i){
		L[i]=min(i,p[i]),R[i]=max(i,p[i]);
		while(!stk.empty()&&stk.top()>=L[i]){
			int x=stk.top();stk.pop();
			Min(L[i],L[x]),Max(R[i],R[x]);
		}
		stk.push(i);
	}
	for(int i=0;i<=(n<<1);++i)  rt[i]=father[i]=-1;
	for(int i=1;i<=(n<<1);++i)  if(R[i]==i){
		father[i]=L[i]-1;
		e[L[i]-1].push_back(i);
	}
	for(int i=0;i<=(n<<1);++i)  if(!e[i].empty()&&rt[i]==-1)  dep[i]=1,dfs(i,i);
}
int LCA(int x,int y){
	if(dep[x]<dep[y])  swap(x,y);
	for(int i=20;~i;--i)  if(dep[fa[x][i]]>=dep[y])  x=fa[x][i];
	if(x==y)  return x;
	for(int i=20;~i;--i)  if(fa[x][i]!=fa[y][i])  x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
int Query(int r1,int r2){
	return (rt[r1]==-1||rt[r2]==-1||rt[r1]!=rt[r2])?0:dep[LCA(r1,r2)]-1;
}
int main(){
	n=in(),m=in();
	for(int i=1;i<=(n<<1);++i)  p[i]=in();
	build();
	while(m--){
		int r1=in(),r2=in();
		printf("%d\n",(!r1&&!r2)?0:Query(r1,r2));
	}
	return 0;
}

NOI 模拟

T2 长寿花

f [ i ] [ j ] f[i][j] f[i][j] 表示给 i i i 个点染 j j j 种颜色的方案数,那么:

f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + f [ i − 1 ] [ j ] × ( j − 1 ) f[i][j]=f[i-1][j-1]+f[i-1][j]\times(j-1) f[i][j]=f[i1][j1]+f[i1][j]×(j1)

就是考虑第 i i i 个是染一个新颜色还是染之前出现过的颜色。

又设 g [ i ] [ j ] g[i][j] g[i][j] 表示 d p dp dp 到第 i i i 行,且第 i i i 行染 j j j 种颜色的方案数,考虑用 g [ i − 1 ] [ k ] g[i-1][k] g[i1][k] 来更新 g [ i ] [ j ] g[i][j] g[i][j],则:

{ g [ i ] [ j ] ⟵ g [ i − 1 ] [ k ] × ( m j ) × f [ a i ] [ j ] × j ! , j ≠ k g [ i ] [ j ] ⟵ g [ i − 1 ] [ k ] × ( ( m j ) − 1 ) × f [ a i ] [ j ] × j ! , j = k \begin{cases} g[i][j]\longleftarrow g[i-1][k]\times \binom m j\times f[a_i][j]\times j!,&j\ne k\\ g[i][j]\longleftarrow g[i-1][k]\times (\binom m j-1)\times f[a_i][j]\times j!,&j= k \end{cases} {g[i][j]g[i1][k]×(jm)×f[ai][j]×j!,g[i][j]g[i1][k]×((jm)1)×f[ai][j]×j!,j=kj=k

( m j ) \binom m j (jm) 是在 m m m 种颜色中选当前的 j j j 中颜色,乘上预处理的 f [ a i ] [ j ] f[a_i][j] f[ai][j] 的方案,而乘 j ! j! j! 是因为这是无序枚举

由于模数不一定是质数,所以不一定会有逆元,不能直接预处理组合数。但是,由于要乘 j ! j! j!,会把除的消掉,不用管。

时间复杂度 O ( S ) O(S) O(S)

#include
using namespace std;
namespace IO{
	const int Rlen=1<<22|1;
	char buf[Rlen],*p1,*p2;
	inline char gc(){
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>
	inline T Read(){
		char c=gc();T x=0,f=1;
		while(!isdigit(c))  f^=(c=='-'),c=gc();
		while( isdigit(c))  x=(x+(x<<2)<<1)+(c^48),c=gc();
		return f?x:-x;
	}
	inline int in()  {return Read<int>();}
}
using IO::in;
const int N=1e6+5,M=5005;
int n,m,P;
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return 1ll*x*y>=P?1ll*x*y%P:x*y;}
void Add(int &x,int y)  {x=add(x,y);}
void Max(int &x,int y)  {if(x<y)x=y;}
void Min(int &x,int y)  {if(x>y)x=y;}
int mx,a[N],C[N],fac[M],f[M][M],g[2][M];
int main(){
	n=in(),m=in(),P=in();
	for(int i=1;i<=n;++i)  a[i]=in(),Max(mx,a[i]);
	f[0][0]=fac[0]=1;
	for(int i=1;i<=mx;++i)  fac[i]=mul(fac[i-1],i);
	for(int i=1;i<=mx;++i)
		for(int j=1;j<=mx;++j)
			f[i][j]=add(f[i-1][j-1],mul(f[i-1][j],j-1));
	C[1]=m;
	for(int i=2;i<=m;++i)  C[i]=mul(C[i-1],m-i+1);
	int t=0,sum=1;
	for(int i=1;i<=n;++i,t^=1){
		memset(g[t],0,sizeof(g[t]));
		for(int j=0;j<=a[i];++j)
			Add(g[t][j],mul(dec(mul(sum,C[j]),mul(g[t^1][j],fac[j])),f[a[i]][j]));
		sum=0;
		for( int j=0;j<=a[i];++j)  Add(sum,g[t][j]);
	}
	printf("%d\n",sum);
	return 0;
}

T3 七十和十七

感觉不算很难,但是比较妙。

n n n 个数排序可以看成两步:先对前 n − 1 n-1 n1 个数排序,再把第 n n n 个数排到对应的位置。

根据题目要求的排序方式,只有当 1 ∼ n − 1 1\sim n-1 1n1 都排好了才会扫到 n n n 来排第 n n n 个。

那么有两种情况:

  • a n = n a_n=n an=n,那么贡献是 0 0 0
  • a n = k ( k ≠ n ) a_n=k(k\ne n) an=k(k=n),那么此时是 1 , 2 , ⋯   , k − 1 , k + 1 , ⋯   , k 1,2,\cdots,k-1,k+1,\cdots,k 1,2,,k1,k+1,,k,我们要把 k k k 放到正确位置,有 2 k − 1 2^{k-1} 2k1 的贡献。

所以从 f n − 1 f_{n-1} fn1 f n f_n fn 总共有 2 0 + 2 1 + ⋯ + 2 n − 2 = 2 n − 1 − 1 2^0+2^1+\cdots+2^{n-2}=2^{n-1}-1 20+21++2n2=2n11 的贡献,再乘个逆元即可。

时间复杂度 O ( n ) O(n) O(n)

#include
using namespace std;
const int N=1e6+5,P=1e9+7;
int n,Mul=2,f[N],inv[N];
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return 1ll*x*y>=P?1ll*x*y%P:x*y;}
int main(){
	scanf("%d",&n),inv[1]=1;
	for(int i=2;i<=n;++i){
		inv[i]=mul(P-P/i,inv[P-P/i*i]);
		f[i]=add(f[i-1],mul(dec(Mul,1),inv[i])),Mul=mul(Mul,2);
	}
	printf("%d\n",f[n]);
	return 0;
}

你可能感兴趣的:(#,校内模拟)