题目大意
有一排n个格子,每次可以对位置1~n-1进行染黑操作,在位置i操作会使i和i+1都变黑。
定义一个操作序列的得分:若前i-1次操作没有全部染黑而第i次染黑了,那么得分为i。
问1~n-1的所有排列的得分和。
模1e9+7
n<=1e6
解题思路
完全不会计数…
考虑设dp直接做,发现不行。
考虑一些别的方法。套路地转化一下统计的东西:
∑p[1..n−1]p的得分=∑i=0..n−2g(i)
∑
p
[
1..
n
−
1
]
p
的
得
分
=
∑
i
=
0..
n
−
2
g
(
i
)
,其中g(i)=得分大于i的p的个数。
显然g(0)=(n-1)!,而其他的怎么算呢?
假设我要算g(i),那么先计算操作i次都不会把整个序列染黑的个数f(i),然后再乘上阶乘即可。
此时分类讨论:
如果没有染黑n-1,那么方案数是
Cin−2∗i!
C
n
−
2
i
∗
i
!
;
如果染黑了n-1,我们小小地容斥一下,是
Ci−1n−2i!−w[i]∗i!
C
n
−
2
i
−
1
i
!
−
w
[
i
]
∗
i
!
,其中w[i]表示i次染色恰好全部染黑的方案数,而我先计算有序的最后再乘i!。现在计算w[i]。
我们发现操作序列排序之后,序列第一位是1,末尾是n-1,相邻元素的差是1或2。那么我们做一下差分,那么差分序列的和是n-2,1和2的个数是确定的,那么我们解个方程得出1和2的个数cnt1与cnt2,那么差分数组的种类数就是
Ccnt1i−1
C
i
−
1
c
n
t
1
,由于原序列第一位是1,是确定的,那么原序列的方案数也是这个。
那么求出w,求出f,求出g,求出答案。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
typedef double db;
const int N=1e6+5,mo=1e9+7;
int fac[N],rev[N],i,cnt1,f[N],w[N],ans,n;
int ksm(int x,int y)
{
if (y<0) y=1ll*(-y)*(mo-2)%(mo-1);
int ret=1;
while (y)
{
if (y&1) ret=1ll*ret*x%mo;
y>>=1;
x=1ll*x*x%mo;
}
return ret;
}
int c(int m,int n)
{
return 1ll*fac[m]*rev[n]%mo*rev[m-n]%mo;
}
void predo(int n)
{
fac[0]=1;
fo(i,1,n) fac[i]=1ll*fac[i-1]*i%mo;
rev[n]=ksm(fac[n],mo-2);
fd(i,n,1) rev[i-1]=1ll*rev[i]*i%mo;
}
int calc(int x)
{
//2*x-n+2=cnt1
cnt1=2*x-n+2;
if (cnt1>x||cnt1<0) return 0;
return c(x,cnt1);
}
int main()
{
freopen("t16.in","r",stdin);
//freopen("t16.out","w",stdout);
scanf("%d",&n);
predo(n);
fo(i,0,n-2)
{
f[i]=1ll*c(n-2,i)*fac[i]%mo;
w[i]=calc(i-1);
f[i]=(f[i]+1ll*c(n-2,i-1)*fac[i]-1ll*w[i]*fac[i])%mo;
if (!i) f[i]=1;
ans=(ans+1ll*f[i]*fac[n-1-i])%mo;
}
if (ans<0) ans+=mo;
printf("%d\n",ans);
}