[BZOJ2729][HNOI2012]排队(组合数学+高精度)

=== ===

这里放传送门

=== ===

题解

这题的有毒之处就在于情况太tm多了。。各种判各种讨论。。
首先做的时候先不考虑任意两个人不同,最后再乘上阶乘。
比较容易想到的第一种情况就是先放下两个老师,然后把男生塞进去。因为老师不能相邻所以两个老师中间先放上一个男生,然后再把剩下的n-1个男生放在三个空位里。之所以说是三个空位是因为那个放在两个老师之间的男生两边的空位是等价的,因为不考虑两个人不同。
但是这样就遗漏了一些情况,首先两个老师被一个女生隔开的情况就没有考虑。那么就把两个老师和一个女生看成一组。那么首先n个男生放好,然后在n+1个空位里面放剩下的m-1个女生,一个空位最多只能放一个。然后再把那个组合随便放到n+m个空位里面的任意一个里就可以了。
如果m<=2的话这样就算结束了,但是如果m>=3的话就还有一种情况没有考虑到,就是三个女生中间夹着两个老师的ABABA这种情况。因为这种情况如果去掉了中间那两个老师和一个女生的组合,两个女生是相邻的。但是上面算第二种情况的时候在加入组合之前两个女生是不可能相邻的。所以这个时候把三个女生和两个老师看成一个组合。首先把女生放好然后选出连续的三个来,一共有m-2种选择,然后这就相当于三个女生缩成了一个,剩下m-2个女生,然后两两之间先放一个男生,剩下n-(m-3)个再随便放,这样就把所有的情况都包括了。
这题需要写高精,为了避免高精乘高精用了质因数分解。注意有些组合数算出来可能是0或者1,这个地方要小心地特判一下。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2010
#define Mod 10000
using namespace std;
int n,m,prm[2100];
bool ext[2100];
struct PrimeRec{
    int d[500];
    PrimeRec(){memset(d,0,sizeof(d));}
    PrimeRec(int k){memset(d,0,sizeof(d));d[0]=k;}
    PrimeRec operator * (const int &a){
        PrimeRec c=*this;
        int tmp=a;
        for(int i=1;i<=prm[0];i++)
          if (tmp%prm[i]==0){
              while (tmp%prm[i]==0){
                  tmp/=prm[i];++c.d[i]; 
              }
              if (tmp==1) break;
          }
        return c;
    }
    PrimeRec operator * (const PrimeRec &a){
        PrimeRec c=*this;
        for (int i=1;i<=prm[0];i++)
          c.d[i]+=a.d[i];
        return c;
    }
    PrimeRec operator / (const PrimeRec &a){
        PrimeRec c=*this;
        for (int i=1;i<=prm[0];i++)
          c.d[i]-=a.d[i];
        return c;
    }
}mul[2100],A;
struct Bignum{
    int len,s[3010];
    Bignum(){len=0;memset(s,0,sizeof(s));}
    Bignum(int k){
        len=0;
        if (k==0) len=1;
        while (k!=0){s[++len]=k%10;k/=10;}
    }
    Bignum operator + (const Bignum &a){
        Bignum c=Bignum();
        c.len=max(len,a.len);
        for (int i=1;i<=c.len;i++){
            c.s[i]+=s[i]+a.s[i];
            c.s[i+1]+=c.s[i]/Mod;
            c.s[i]%=Mod;
        }
        while (c.s[c.len+1]>0){
            ++c.len;
            c.s[c.len+1]+=c.s[c.len]/Mod;
            c.s[c.len]%=Mod;
        }
        return c;
    }
    Bignum operator * (const int &a){
        Bignum c=Bignum();
        c.len=len;
        for (int i=1;i<=c.len;i++){
            c.s[i]+=s[i]*a;
            c.s[i+1]+=c.s[i]/Mod;
            c.s[i]%=Mod;
        }
        while (c.s[c.len+1]>0){
            ++c.len;
            c.s[c.len+1]+=c.s[c.len]/Mod;
            c.s[c.len]%=Mod;
        }
        return c;
    }
    Bignum operator * (const PrimeRec &a){
        Bignum c=*this;
        bool flag=false;
        for (int i=1;i<=prm[0];i++)
          for (int j=1;j<=a.d[i];j++)
            {c=c*prm[i];flag=true;}
        if (flag==false) c=c*a.d[0];//要考虑这个数是否为1
        return c;
    }
    void print(){
        printf("%d",s[len]);
        for (int i=len-1;i>=1;i--){
            if (s[i]/1000==0) printf("0");
            if (s[i]/100==0) printf("0");
            if (s[i]/10==0) printf("0");
            printf("%d",s[i]);
        }
        printf("\n");
    }
}ans,ans1,ans2;
void get_prime(){
    for (int i=2;i<=N;i++){
        if (ext[i]==false) prm[++prm[0]]=i;
        for (int j=1;j<=prm[0];j++){
            if (i*prm[j]>N) break;
            ext[i*prm[j]]=true;
            if (i%prm[j]==0) break;
        }
    }
}
PrimeRec C(int n,int m){
    if (n<m) return PrimeRec();
    if (n==m||m==0) return PrimeRec(1);//特判1的情况
    PrimeRec now=mul[m]*mul[n-m];
    now=mul[n]/now;
    return now;
}
PrimeRec calc(int n,int m) {return C(n+m-1,m-1);}
int main()
{
    scanf("%d%d",&n,&m);
    if (n==0&&m==0){printf("0\n");return 0;}
    get_prime();
    for (int i=1; i<=2010;i++) mul[i]=mul[i-1]*i;
    ans=Bignum(1);
    ans=ans*calc(n-1,3);
    ans=ans*C(n+3,m);

    ans1=Bignum(1);
    ans1=ans1*C(n+1,m-1);
    ans1=ans1*(n+m);

    if (m<=2) {
        ans=ans+ans1;
        A=mul[n]*mul[m]*2;
        ans=ans*A;
        ans.print();
        return 0;
    }

    ans2=Bignum(1);
    ans2=ans2*calc(n-(m-3),m-1);
    ans2=ans2*(m-2);

    ans=ans+ans1+ans2;
    A=mul[n]*mul[m]*2; 
    ans=ans*A;
    ans.print();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值