P8923 『MdOI R5』Many Minimizations
先考虑给定
设
可以发现对于每一个
同时设
每次操作相当于是往堆中先加入两个
去掉一些可以快速计算的东西之后,我们实际上只需要考虑过程结束时堆中剩下的数之和。
我们考虑拆开每个数的贡献。具体地,钦定一个
现在只需要对所有
对于一个
每次操作有
可以发现,每一个
但这还不够,我们需要进一步优化这个算法。
考虑把这个问题转化为格路计数。
对于一条当前意义下的路径,每次找到第一个对
这样我们就把对
此时问题转化为了:
- 起点为横坐标为
0 的任意一个点。 - 每次有
x 种方案往右下,有m-x 种方案往右上走。 - 终点为横坐标为
n 的任意一个点。 - 路径上所有点纵坐标都
\ge 0 且至少有一个点纵坐标为0 。
设走到
路径上至少有一个点纵坐标为
令
根据经典的反射容斥方法可以得到:
代回我们要求的式子里可以得到:
推到这里,我们已经可以非常简单地在
再用你喜欢的方式求出自然数幂和带进多项式计算,时间复杂度
进一步地,可以用卷积求出这个多项式,然后将自然数幂和代入计算。时间复杂度
参考代码(
#include <bits/stdc++.h>
using namespace std;
#define N 5005
int n,m,MOD,ans,pw[N],inv[N],z[N],bn[N][N],o[N][N];
void W(int &x,int y) {x+=y;if(x>=MOD) x-=MOD;}
int add(int x,int y) {x+=y;return x<MOD?x:x-MOD;}
int qPow(int x,int y)
{
int res=1;
for(;y;y/=2,x=1ll*x*x%MOD) if(y&1)
res=1ll*res*x%MOD;return res;
}
void init(int n)
{
pw[0]=o[0][0]=1;
for(int i=1;i<=n;++i) pw[i]=1ll*pw[i-1]*m%MOD;
for(int i=1;i<=n+1;++i)
inv[i]=i>1?1ll*inv[MOD%i]*(MOD-MOD/i)%MOD:1;
for(int i=0;i<=n;++i) for(int j=0;j<=i;++j)
bn[i][j]=j?add(bn[i-1][j],bn[i-1][j-1]):1;
for(int i=1;i<=n;++i) for(int j=1;j<=i;++j)
o[i][j]=(1ll*o[i-1][j]*j+o[i-1][j-1])%MOD;
}
int main()
{
scanf("%d %d %d",&n,&m,&MOD);init(n);
for(int i=0,t1,t2;i<=n;++i)
{
t1=0;
for(int j=max(i,n-i);j<=n;++j)
W(t1,1ll*(j-i)*(bn[n][j]-bn[n][j+1]+MOD)%MOD);
for(int j=0;j<=n-i;++j)
{
t2=1ll*t1*bn[n-i][j]%MOD*pw[n-i-j]%MOD;
W(z[i+j],(j&1?MOD-t2:t2));
}
}
for(int i=0,t1,t2;i<=n;++i)
{
t1=0;t2=1;
for(int j=0;j<=i;++j)
{
t2=1ll*t2*(m-j+1)%MOD;
W(t1,1ll*t2*o[i][j]%MOD*inv[j+1]%MOD);
}W(ans,1ll*t1*z[i]%MOD);
}ans=add(MOD-ans,1ll*m*(m+1)/2%MOD*n%MOD*qPow(m,n-1)%MOD);
printf("%d\n",ans);return 0;
}