[agc023c]Painting Machines

题目大意

有一排n个格子,每次可以对位置1~n-1进行染黑操作,在位置i操作会使i和i+1都变黑。
定义一个操作序列的得分:若前i-1次操作没有全部染黑而第i次染黑了,那么得分为i。
问1~n-1的所有排列的得分和。
模1e9+7
n<=1e6

解题思路

完全不会计数…
考虑设dp直接做,发现不行。
考虑一些别的方法。套路地转化一下统计的东西: p[1..n1]p=i=0..n2g(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,那么方案数是 Cin2i! C n − 2 i ∗ i !
如果染黑了n-1,我们小小地容斥一下,是 Ci1n2i!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,那么差分数组的种类数就是 Ccnt1i1 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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值