感觉最近状态真是烂到爆。。
首先不妨令序列有序,然后再乘n!即可。
一上来先想到可以倍增,设f[i][j]表示在1~j中选i个数,那么有j->2j,便可以通过枚举一边选了几个得到。但是算错了复杂度以为是 O(n3logn) 的。。(矩阵乘习惯了一倍增就感觉是三方挂logn。。)(没想到看了题解以后这竟然就是标算。。)
然后又想,既然倍增是三次方的。。。那只能看看暴力转移了。 f(i,j)=j∑j−1k=1f(i−1,k) ,即f(i,j)=j*s(i-1,j-1)。那么不妨猜测f(i,x)是一个关于x的若干项多项式,而通过这个递推式可以发现f(i,x)会比f(i-1,x)次数大2,前缀和会使其次数+1,然后它又乘了一个x,而当i=1的时候我们知道它确实等于 x(x+1)2 ,是一个3次多项式,即最终答案会是一个2n+1次多项式。那么我们不妨dp求出f(n,1~2n+1)以内的值,然后拉格朗日插值即可。
随后又想到了一个容斥做法。(终于明白容斥是干什么啦!)
设f(i)为最终答案,则有 f(i)=∑i−1j=0(−1)i−j−1(∑Ak=1kj)f(j)i 。这是因为考虑每个数k,首先加上它出现1或2次的方案数,再减去它出现2或3次的方案数,再加上它出现3或4次的方案数。。直到最后,但是这样的话一种方案数中i个数都会被考虑从而计算一遍,所以每种方案都被考虑了i遍,那么最后除以i就可以了。
这个其实也不能算容斥啦。一般的容斥应该是什么样的呢?应该是算至少出现多少次的方案数,那么从小到大考虑至少出现次数,计算至少出现i次的时候,它的系数应该等于一个方案之前被统计次数的相反数,因为至少出现i次在之后不可能被考虑到了,而最终这种方案的系数需要是0,那么现在它就必须是0!
代码(拉格朗日插值):
#include
#include
using namespace std;
#include
#include
#include
const int A=1e9+5,N=500+5,P=1e9+5;
int p;
typedef long long LL;
LL power(LL prod,int x)
{
LL ans=1;
for(;x;x>>=1)
{
if(x&1)(ans*=prod)%=p;
(prod*=prod)%=p;
}
return ans;
}
LL f[N][N<<1];
int main()
{
freopen("local4.in","r",stdin);
//freopen("local4.out","w",stdout);
int a,n;
scanf("%d%d%d",&a,&n,&p);
int m=n+1;
for(int j=1;j<=m;++j)f[1][j]=j+f[1][j-1];
for(int i=2;i<=n;++i)
for(int j=i;j<=m;++j)
{
f[i][j]=(j*f[i-1][j-1]+f[i][j-1])%p;
//printf("f(%d,%d)=%I64d\n",i,j,f[i][j]);
}
LL ans=0;
if(a<=m)ans=f[n][a];
else
{
LL prod=1;
for(int i=2;i<=m;++i)prod=prod*(a-i)%p*power(1-i,p-2)%p;
(ans+=f[n][1]*prod)%=p;
//printf("[i=1]:%I64d\n",(f[n][1]*prod%p+p)%p);
for(int i=2;i<=m;++i)
{
prod=prod*(a-(i-1))%p*power(a-i,p-2)%p*power(i-1,p-2)%p*(i-1-m)%p;
(ans+=f[n][i]*prod)%=p;
//printf("[i=%d]:%I64d\n",i,(f[n][i]*prod%p+p)%p);
}
ans=(ans+p)%p;
}
//printf("ans=%I64d\n",ans);
for(int i=n;i;--i)(ans*=i)%=p;
cout<
代码(容斥):
#include
#include
using namespace std;
typedef long long LL;
const int N=500+5;
LL com[N][N],f[N],coe[N];
int p;
LL power(LL prod,int x)
{
LL ans=1;
for(;x;x>>=1)
{
if(x&1)(ans*=prod)%=p;
(prod*=prod)%=p;
}
return ans;
}
int main()
{
freopen("local4.in","r",stdin);
int a,n;
scanf("%d%d%d",&a,&n,&p);
for(int i=n+1;i>=0;--i)com[i][0]=1;
for(int i=1;i<=n+1;++i)
for(int j=i;j;--j)
com[i][j]=(com[i-1][j-1]+com[i-1][j])%p;
coe[0]=a;
for(int i=1;i<=n;++i)coe[i]=coe[i-1]*a%p;
for(int i=1;i<=n;++i)
{
for(int j=i-1,sgn=1;j>=0;--j,sgn=-sgn)(coe[i]+=sgn*com[i+1][j]*coe[j])%=p;
(coe[i]*=power(i+1,p-2))%=p;
//printf("coe[%d]=%I64d\n",i,coe[i]);
}
f[0]=1;
for(int i=1;i<=n;++i)
{
for(int j=i-1,sgn=1;j>=0;--j,sgn=-sgn)(f[i]+=sgn*coe[i-j]*f[j])%=p;
(f[i]*=power(i,p-2))%=p;
//printf("f[%d]=%d\n",i,f[i]);
}
LL ans=(f[n]+p)%p;
for(int i=n;i;--i)(ans*=i)%=p;
cout<
总结:
①一定要认真算时间复杂度!
②对于有一项特别大的dp,也不一定非要矩乘/倍增呀,说不定是多项式!
③容斥就是说从大到小枚举集合,保证当前集合系数是0.
④如果点选取得当,比如x是1~n,拉格朗日插值是可以做到O(n)的。
⑤注意有序和无序之间的相互转化,统计有序的时候可以考虑无序,而无序的时候又可以先考虑有序。