jzoj 4391. 【GDOI2016模拟3.16】装饰 组合计数

Description
这里写图片描述

Input
一行四个整数, M,R,G,B M , R , G , B

Output
一行一个整数,表示可行解数量 (mod 109+7) ( m o d   10 9 + 7 )

Sample Input

2 2 1 1

Sample Output

4

Data Constraint
这里写图片描述

Hint
这里写图片描述

分析:
因为只有三种颜色,所以相邻两列必有至少一种相同颜色,所以只要第一列顺序确定,剩下的列只有颜色是不定的,位置已经确定。记录每一列缺少的颜色为状态,这样就相当于有 x=MR x = M − R X X y=MG Y Y z=MB Z Z ,要求构造一个长度为M的序列,根据条件 3 3 ,相邻两个缺少颜色不能相同,所以求这样序列的个数。

我们发现,设序列以X开头,并使 y>z y > z 。每一个 X X ,会把序列分成x段或 x1 x − 1 段。假设把序列分成了 g g 段,我们枚举偶数段为e,奇数段即为 ge g − e 。因为偶数段不能为空(两个 X X 不能相邻),所以Y剩下 ye y − e Z Z 剩下ze个。我们设以 Y Y 开头的序列有sumy个,以 Z Z 开头的序列有sumz个,因为每一个 Y Y 开头奇数序列可以使(ye)(ze) 1 1 ,同理,Y开头奇数序列可以加 1 1 ,但是,偶数块差为0,所以有 sumysumz=yz s u m y − s u m z = y − z 。又因为 sumy+sumz=ge s u m y + s u m z = g − e ,这样就可以解出 sumy,sumz s u m y , s u m z 了,但是要保证这两个数非负且为整数。

那方案数就很好统计了,我们还剩下 r=yesumy r = y − e − s u m y Y Y Z,可以看做 YZ Y Z 对和 ZY Z Y 对,每一个对都可以插入任何一个块中,但是只能有 YZ Y Z ZY Z Y 一个合法,所以相当于把 r r 个小球放入g个盒子中,可以不放,方案数为 (r+g1g1) ( r + g − 1 g − 1 )

还有就是奇偶数块的排列,我们可以相当于把 g g 块中的e块选出来放偶数块,所以方案数为 (ge) ( g e )

对于每一个奇数块,有 sumy s u m y 个以 Y Y 开头,所以要选出sumy个出来放,方案数为 (gesumy) ( g − e s u m y )

每一个偶数块,可以以 Y Y 开头,也可以以Z头,方案数为 2e 2 e

把这些乘起来就是对于 e e 个偶数块的方案。

然后把Y为序列头,及 Z Z 为序列头的跑一下,最后给答案乘2,因为开头一列可以反过来。可以考虑预处理出逆元,可以跑很快,我的代码没有预处理,但是还是过了。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const LL mod=1e9+7;
const LL maxn=1e6+7;

using namespace std;

LL n,x,y,z,ans;
LL jc[maxn];

void init()
{
    LL R,G,B;
    scanf("%lld%lld%lld%lld",&n,&R,&G,&B);
    x=n-R; y=n-G; z=n-B;
    jc[0]=1; jc[1]=1;
    for (LL i=2;i<=maxn;i++) jc[i]=(jc[i-1]*i)%mod;
}

LL power(LL x,LL y)
{
    if (y==0) return 1;
    if (y==1) return x;
    LL c=power(x,y/2);
    c=(c*c)%mod;
    if (y%2) c=(c*x)%mod;
    return c;
}

LL c(LL n,LL m)
{
    if (n<m) return 0;
    return jc[n]*power(jc[m]*jc[n-m]%mod,mod-2)%mod;
}

void calc(LL x,LL y,LL z)
{
    if (y<z) swap(y,z);
    for (LL i=0;i<=x;i++)
    {
        if (((x-i+y-z)%2) || ((x-i-y+z)%2)) continue;
        LL sumy=(x-i+y-z)/2;
        LL sumz=(x-i-y+z)/2;
        if ((sumy<0) || (sumz<0)) continue;
        LL r=y-i-sumy;
        ans=(ans+c(r+x-1,x-1)%mod*c(x,i)%mod*c(x-i,sumy)%mod*power(2,i)%mod)%mod;
    }
    x--;
    for (LL i=0;i<=x;i++)
    {
        if (((x-i+y-z)%2) || ((x-i-y+z)%2)) continue;
        LL sumy=(x-i+y-z)/2;
        LL sumz=(x-i-y+z)/2;
        if ((sumy<0) || (sumz<0)) continue;
        LL r=y-i-sumy;
        ans=(ans+c(r+x-1,x-1)%mod*c(x,i)%mod*c(x-i,sumy)%mod*power(2,i)%mod)%mod;
    }
}

int main()
{
    init(); 
    calc(x,y,z);
    calc(y,x,z);
    calc(z,x,y);
    ans=ans*2%mod;
    printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值