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\)数量
- \(a_i<i,1\geq\sum_{i=1}^n[a_i> k]\)
- \(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\),进行变换
计算\(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;
}
浮世苍茫,不过瞬逝幻梦
善恶爱诳,皆有定数
于命运之轮中
吞噬于黄泉之冥暗
呜呼,吾乃梦之戍人
幻恋之观者
唯于万华镜中,永世长存