2021.9.9模拟赛

2021.9.9模拟赛

⼀颗\(n\)个点的有根树,\(1\)号节点是根。节点\(i\)有权值\(val_i\)。可以从根节点出发\(k\)次,只能从⽗亲往⼉⼦⾛,到达⼀个节点可以获得该节点权值的收益,多次到达⼀个节点只能获得⼀次收益。求最⼤收益。

\(n\leq 4000000,val\leq 10^9\)

我们按照权重进行长链剖分(按照校长的建议所表述为长链剖分)

这样选取最长的\(k\)条链,所得到的就是答案,因为不存在断点(存在则非最大的)且无重(链剖分)

我们使用非递归,进行基数排序,时间复杂度\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define read read1<ll>()
# define Type template<typename T>
Type T read1(){
	T t=0;
	char k;
	bool vis=0;
	do (k=getchar())=='-'&&(vis=1);while('0'>k||k>'9');
	while('0'<=k&&k<='9')t=(t<<3)+(t<<1)+(k^'0'),k=getchar();
	return vis?-t:t;
}
# define fre(k) freopen(k".in","r",stdin);freopen(k".out","w",stdout)
# define ll long long
int s,k,son[4000005],tot,fa[4000005];
ll val,a[4000005],b[4000005];
int rk[262144],hv[262144];
void Rsort(){
	for(int i=1;i<=tot;++i)++hv[b[i]&262143];
	for(int i=1;i<262144;++i)hv[i]+=hv[i-1];
	for(int i=tot;i;--i)a[hv[b[i]&262143]--]=b[i];
	
	memset(hv,0,sizeof(hv));
	for(int i=1;i<=tot;++i)	++hv[(a[i]>>18)&262143];
	for(int i=1;i<262144;++i)hv[i]+=hv[i-1];
	for(int i=tot;i;--i)b[hv[(a[i]>>18)&262143]--]=a[i];
	
	memset(hv,0,sizeof(hv));
	for(int i=1;i<=tot;++i)++hv[b[i]>>36];
	for(int i=1;i<262144;++i)hv[i]+=hv[i-1];
	for(int i=tot;i;--i)a[hv[b[i]>>36]--]=b[i];
}
int main(){fre("tree");
	s=read;k=read;val=read;
	a[1]=val;
	for(int i=2;i<=s;++i){
		val=(val*23333333ll+6666666ll)%1000000007ll;
		fa[i]=(val^23333333ll)%(i-1)+1;
		a[i]=val;
	}for(int i=s;i;--i){
		a[i]+=a[son[i]];
		if(a[i]>=a[son[fa[i]]])son[fa[i]]=i;
	}son[0]=0;
	for(int i=1;i<=s;++i)
		if(a[i]!=a[son[fa[i]]])b[++tot]=a[i];
	Rsort();
	ll ans=0;
	if(k>tot)k=tot;
	for(int i=tot;i>tot-k;--i)ans+=a[i];
	cout<<ans;
	return 0;
}
/*
4000000 233333 12345678
742722259477254
*/

就差⼀点

冒泡排序是一个简单的排序算法,其时间复杂度为 \(O(n^2)\)

dfc有一个大小为 \(n\) 的排列 \(p_{1,2,\ldots ,n}\),他想对这个排列进行冒泡排序,于是他写了下面这份代码:

for(int i=1;i<=k;i++)
	for(int j=1;j<n;j++)
		if(p[j]>p[j+1]) swap(p[j],p[j+1]);

细心的选手不难发现 dfc 手抖把第一行中的 \(n\) 打成了 \(k\),所以当 \(k\) 比较小时,这份代码可能会出错。

dfc 发现当这份代码出错时,可能就差一点就能把这个排列排序,他定义一个排列就差一点能被排序,当且仅当这个排列存在一个大小为 \(n-1\) 的上升子序列。

dfc 想知道,对于给定的 \(n,k\),有多少种不同的排列满足对这个排列运行上述代码后,这个排列就差一点能被排序。

由于答案可能很大,dfc 只需要知道答案对质数 \(mod\) 取模的结果。

通过反序表,转化题意为:满足下列条件__之一__的序列\(a\)数量

  1. \(a_i<i,1\geq\sum_{i=1}^n[a_i> k]\)
  2. \(a_i<i,\exists 1\leq l\leq r\leq n,(a_i>k\Leftrightarrow i\in [l,r])\)

第一种情况有\(k![x^0/x^1]\prod_{i=k+1}^n((k+1)+(i-k-1)x)\)

第二种情况则有\(k!\sum_{i=1}^{n-k-2}i(k+1)^i\)种(减去与情况一重复的情况)

可用等差数列求和公式求出情况一的方案数,情况二直接计算

这样时间复杂度\(O(Tn)\)

考虑优化

对于\(f(x)=\sum_{i=1}^nix^i\),进行变换

\[f(x)=x(\sum_{i=1}^nx^i)'\\ =x(x\frac{1-x^{n}}{1-x})'\\ =\frac{nx^{n+1}-(n+1)x^n+1}{(1-x)^2} \]

计算\(k!\)则使用快速阶乘算法

由此,我们便得到了时间复杂度\(O(T\sqrt n)\)的算法

$\tt O(Tn)$
#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define read read1<ll>()
# define Type template<typename T>
Type T read1(){
	T t=0;
	char k;
	bool vis=0;
	do (k=getchar())=='-'&&(vis=1);while('0'>k||k>'9');
	while('0'<=k&&k<='9')t=(t<<3)+(t<<1)+(k^'0'),k=getchar();
	return vis?-t:t;
}
# define fre(k) freopen(k".in","r",stdin);freopen(k".out","w",stdout)
# define ll long long
int mod;
ll qkpow(ll n,int m){
	ll t=1;
	for(;m;m>>=1,n=n*n%mod)
		if(m&1)t=t*n%mod;
	return t;
}
ll f(int x,int n){return ((n*x-n-1+mod)%mod*qkpow(x,n)+1)%mod*qkpow((x-1ll)*(x-1)%mod,mod-2)%mod*x%mod;}
int main(){fre("almostthere");
	for(int T=read;T--;){
		int n=read,k=read;mod=read;
		if(k>n)k=n;
		ll t=1,v=1ll*(n-k-1)*(n-k)/2%mod;
		for(int i=1;i<=k;++i)t=t*i%mod;
		if(k==n){printf("%lld\n",t);continue;}
		if(k==n-1){printf("%lld\n",t*n%mod);continue;}
		printf("%lld\n",t*(qkpow(k+1,n-k-1)*(k+1+v)%mod+f(k+1,n-k-2))%mod);
	}
	return 0;
}
$\tt O(T\sqrt n)$
咕咕咕,有时间再来填坑(上文求k!改为快速阶乘即可

题外话:快速阶乘算法如果将其中的每若干连续数字的积的__实际值__存下来,则可以得到\(O(T\log_{2^{64}}n!\approx931T)\)的算法

#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define read read1<ll>()
# define Type template<typename T>
Type T read1(){
	T t=0;
	char k;
	bool vis=0;
	do (k=getchar())=='-'&&(vis=1);while('0'>k||k>'9');
	while('0'<=k&&k<='9')t=(t<<3)+(t<<1)+(k^'0'),k=getchar();
	return vis?-t:t;
}
# define fre(k) freopen(k".in","r",stdin);freopen(k".out","w",stdout)
# define ll long long
int mod;
ll qkpow(ll n,int m){
	ll t=1;
	for(;m;m>>=1,n=n*n%mod)
		if(m&1)t=t*n%mod;
	return t;
}
ll f(int x,int n){return ((n*x-n-1+mod)%mod*qkpow(x,n)+1)%mod*qkpow((x-1ll)*(x-1)%mod,mod-2)%mod*x%mod;}
ll a[931];int b[931],tot;
# define LLMAX 9223372036854775807ll
int main(){//fre("almostthere");
	ll t=1;
	for(int i=1;i<=5000;++i){
		t*=i;
		if(LLMAX/(i+1)+1<=t)a[tot]=t,b[tot]=i,t=1,++tot;
	}
	for(int T=read;T--;){
		int n=read,k=read;mod=read;
		if(k>n)k=n;
		ll t=1,v=1ll*(n-k-1)*(n-k)/2%mod;
		int la=2;
		for(int i=0;i<931;++i)
			if(b[i]<=k)t=a[i]%mod*t%mod,la=b[i]+1;
			else break;
		for(int i=la;i<=k;++i)t=t*i%mod;
		if(k==n){printf("%lld\n",t);continue;}
		if(k==n-1){printf("%lld\n",t*n%mod);continue;}
		printf("%lld\n",t*(qkpow(k+1,n-k-1)*(k+1+v)%mod+f(k+1,n-k-2))%mod);
	}
	return 0;
}
posted @ 2021-09-09 22:01  ファイナル  阅读(79)  评论(0编辑  收藏  举报