#189 stat(动态规划)

时间:2023-03-09 16:15:30
#189 stat(动态规划)

  容易想到固定第一个排列为1~n,算出答案后乘上n!即可,但这样就离正解走远了。

  考虑排列dp的一般套路,将数从大到小加入排列,这样每个位置第一次填数时(不管是第一个还是第二个排列)其贡献就确定了。

  显然当前两个排列有用的信息仅仅是两个排列都填了、仅第一个排列填了、仅第二个排列填了、两个排列都没填的位置数量。并且注意到仅第一个排列填了和仅第二个排列填了的位置数量是相等的,同时dp过程中也知道当前填了多少个数,所以dp时只要记录两个排列都填了的位置数量即可。

  于是设f[i][j][k]为填到i时,有j个位置两个排列都填了,当前贡献之和为k时的方案数,瞎转移即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 72
#define P 1000000007
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int n,m,f[N][N][N*N];
void inc(int &x,int y){x+=y;if (x>=P) x-=P;}
int C(int n,int m){return 1ll*n*(n-1)/2%P;}
int main()
{
freopen("stat.in","r",stdin);
freopen("stat.out","w",stdout);
n=read(),m=read();
f[n+1][0][0]=1;
for (int i=n+1;i>1;i--)
{
for (int x=0;x<=n+1-i;x++)
{
int y=n+1-i-x,z=n-x-y*2;
if (y>=0&&z>=0)
for (int k=0;k<=n*n;k++)
if (f[i][x][k])
{
if (z>=2) inc(f[i-1][x][k+(i-1)*2],1ll*f[i][x][k]*z%P*(z-1)%P);
if (z>=1) inc(f[i-1][x+1][k+(i-1)],2ll*f[i][x][k]*z%P*y%P);
if (z>=1) inc(f[i-1][x+1][k+(i-1)],1ll*f[i][x][k]*z%P);
if (y>=1) inc(f[i-1][x+2][k],1ll*f[i][x][k]*y%P*y%P);
}
}
}
int ans=0;
for (int i=m;i<=n*n;i++) inc(ans,f[1][n][i]);
cout<<ans;
return 0;
}