计蒜客暑假第一阶段第六场 d题

Persona5 is a famous video game.

In the game, you are going to build relationship with your friends.

You have NN friends and each friends have his upper bound of relationship with you. Let's consider the i^{th}ith friend has the upper bound U_iUi​. At the beginning, the relationship with others are zero. In the game, each day you can select one person and increase the relationship with him by one. Notice that you can't select the person whose relationship with you has already reach its upper bound. If your relationship with others all reach the upper bound, the game ends.

It's obvious that the game will end at a fixed day regardless your everyday choices. Please calculate how many kinds of ways to end the game. Two ways are said to be different if and only if there exists one day you select the different friend in the two ways.

As the answer may be very large, you should output the answer mod 10000000071000000007

Input Format

The input file contains several test cases, each of them as described below.

  • The first line of the input contains one integers NN (1 \le N \le 1000000)(1≤N≤1000000), giving the number of friends you have.
  • The second line contains NN integers. The i^{th}ith integer represents U_iUi​ ( 1 \le U_i \le 1000000)(1≤Ui​≤1000000), which means the upper bound with i^{th}ith friend. It's guarantee that the sum of U_iUi​ is no more than 10000001000000.

There are no more than 1010 test cases.

Output Format

One line per case, an integer indicates the answer mod 10000000071000000007.

样例输入

3
1 1 1
3
1 2 3

样例输出

6
60

题意:

你有n个朋友,开始你们的默契为0,每天都可以增加一,当你和你的朋友的默契达到每一个人的最高默契值(题目中给出的n个数代表n个朋友的默契值)结束,问你有多少种方式达到最高默契

思路:

这个题目我们抽象成有n个球,有a1+a2+a3+......+an个盒子,第i个球所占据的盒子数为ai,要把这些这些球放到盒子里,一共有几种放法?

我们假设一共有N个盒子,首先,我们从N个盒子中选择a1个盒子放第一个球,一共有C(N,a1)种,然后,我们从剩余的N-a1个盒子中选择a2个盒子放第第二个球,一共有C(N-a1,a2)种,依次类推,最后我们得到公式:

   公式一:种类数=C(N,a1)*C(N-a1,a2)*C(N-a1-a2,a3)*........*C(an,an)。

   公式二:种类数=(和的阶乘)/(阶乘的和)。

TL解法:直接计算公式二,超时。

AC解法(计算公式一):我们首先通过Lucas定理计算组合数,然后每计算出一个组合数,运用快速积取模累乘(题目要求)。

这个地方不能够全部累乘完之后再取模,因为全部累乘完之后的数字太大了,是一个高精度数,所以必须算出一个组合数就要累乘然后取模,防止出现高精度数。

通过Lucas定理计算组合数,通过费马小定理求逆元。具体不懂看:

传送们:http://www.cnblogs.com/linyujun/p/5199684.html         https://www.cnblogs.com/linyujun/p/5194184.html

其实Lucas定理就是通过公式将a,b变小,然后运用普通的方法求组合数。

代码:

直接计算计算公式二的代码:

运用大数运算直接求解公式二,超时。

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
ll mod1=1000000007;
ll n;
const int L=99999;
ll root;
int a[L];
string add(string a,string b)//只限两个非负整数相加
{
    string ans;
    int na[L]={0},nb[L]={0};
    int la=a.size(),lb=b.size();
    for(int i=0;i<la;i++) na[la-1-i]=a[i]-'0';
    for(int i=0;i<lb;i++) nb[lb-1-i]=b[i]-'0';
    int lmax=la>lb?la:lb;
    for(int i=0;i<lmax;i++) na[i]+=nb[i],na[i+1]+=na[i]/10,na[i]%=10;
    if(na[lmax]) lmax++;
    for(int i=lmax-1;i>=0;i--) ans+=na[i]+'0';
    return ans;
}
string fac(ll n)
{
    string ans;
    if(n==0) return "1";
    fill(a,a+L,0);
    int s=0,m=n;
    while(m) a[++s]=m%10,m/=10;
    for(int i=n-1;i>=2;i--)
    {
        int w=0;
        for(int j=1;j<=s;j++) a[j]=a[j]*i+w,w=a[j]/10,a[j]=a[j]%10;
        while(w) a[++s]=w%10,w/=10;
    }
    while(!a[s]) s--;
    while(s>=1) ans+=a[s--]+'0';
    return ans;
}
string mul(string a,string b)//高精度乘法a,b,均为非负整数
{
    string s;
    int na[L],nb[L],nc[L],La=a.size(),Lb=b.size();//na存储被乘数,nb存储乘数,nc存储积
    fill(na,na+L,0);fill(nb,nb+L,0);fill(nc,nc+L,0);//将na,nb,nc都置为0
    for(int i=La-1;i>=0;i--) na[La-i]=a[i]-'0';//将字符串表示的大整形数转成i整形数组表示的大整形数
    for(int i=Lb-1;i>=0;i--) nb[Lb-i]=b[i]-'0';
    for(int i=1;i<=La;i++)
        for(int j=1;j<=Lb;j++)
        nc[i+j-1]+=na[i]*nb[j];//a的第i位乘以b的第j位为积的第i+j-1位(先不考虑进位)
    for(int i=1;i<=La+Lb;i++)
        nc[i+1]+=nc[i]/10,nc[i]%=10;//统一处理进位
    if(nc[La+Lb]) s+=nc[La+Lb]+'0';//判断第i+j位上的数字是不是0
    for(int i=La+Lb-1;i>=1;i--)
        s+=nc[i]+'0';//将整形数组转成字符串
    return s;
}
int sub(int *a,int *b,int La,int Lb)
{
    if(La<Lb) return -1;//如果a小于b,则返回-1
    if(La==Lb)
    {
        for(int i=La-1;i>=0;i--)
            if(a[i]>b[i]) break;
            else if(a[i]<b[i]) return -1;//如果a小于b,则返回-1

    }
    for(int i=0;i<La;i++)//高精度减法
    {
        a[i]-=b[i];
        if(a[i]<0) a[i]+=10,a[i+1]--;
    }
    for(int i=La-1;i>=0;i--)
        if(a[i]) return i+1;//返回差的位数
    return 0;//返回差的位数
}
string div(string n1,string n2,int nn)//n1,n2是字符串表示的被除数,除数,nn是选择返回商还是余数
{
     string s,v;//s存商,v存余数
     int a[L],b[L],r[L],La=n1.size(),Lb=n2.size(),i,tp=La;//a,b是整形数组表示被除数,除数,tp保存被除数的长度
     fill(a,a+L,0);fill(b,b+L,0);fill(r,r+L,0);//数组元素都置为0
     for(i=La-1;i>=0;i--) a[La-1-i]=n1[i]-'0';
     for(i=Lb-1;i>=0;i--) b[Lb-1-i]=n2[i]-'0';
     if(La<Lb || (La==Lb && n1<n2)) {
            //cout<<0<<endl;
     return n1;}//如果a<b,则商为0,余数为被除数
     int t=La-Lb;//除被数和除数的位数之差
     for(int i=La-1;i>=0;i--)//将除数扩大10^t倍
        if(i>=t) b[i]=b[i-t];
        else b[i]=0;
     Lb=La;
     for(int j=0;j<=t;j++)
     {
         int temp;
         while((temp=sub(a,b+j,La,Lb-j))>=0)//如果被除数比除数大继续减
         {
             La=temp;
             r[t-j]++;
         }
     }
     for(i=0;i<L-10;i++) r[i+1]+=r[i]/10,r[i]%=10;//统一处理进位
     while(!r[i]) i--;//将整形数组表示的商转化成字符串表示的
     while(i>=0) s+=r[i--]+'0';
     //cout<<s<<endl;
     i=tp;
     while(!a[i]) i--;//将整形数组表示的余数转化成字符串表示的</span>
     while(i>=0) v+=a[i--]+'0';
     if(v.empty()) v="0";
     //cout<<v<<endl;
     if(nn==1) return s;
     if(nn==2) return v;
}
ll mod(string a,ll b)//高精度a除以单精度b
{
    ll d=0;
    for(int i=0;i<a.size();i++) d=(d*10+(a[i]-'0'))%b;//求出余数
    return d;
}
int main()
{
    while ((scanf("%lld",&n)!=EOF))
    {
        ll sum=0;
        string op1="1";
        for(int i=0;i<n;i++)
        {
             cin>>root;
             sum+=root;
             op1=mul(op1,fac(root));
        }
        string op2=fac(sum);
        ll op3=mod(div(op2,op1,1),mod1);
        cout<<op3<<endl;
    }
}

正确思路计算公式一的代码:

​
#include<bits/stdc++.h>
#define mod 1000000007
typedef long long ll;
using namespace std;
ll n;
ll lc[1000002];//lc[i]保存的是i的阶乘%mod
void jiecheng()//填充lc数组
{
    lc[0]=1;
    lc[1]=1;
    for(ll i=2;i<1000002;i++)
        lc[i]=lc[i-1]*i%mod;
}
ll power(ll a,ll b)//这个函数是计算a^b%mod的,快速幂取模
{
    ll t=1;
    while (b)
    {
        if(b&1)//这个条件如果满足,说明b是奇数
            t=t*a%mod;
        a=a*a%mod;
        b=b>>1;//这个地方是将b/2
    }
    return t;
}
ll inv(ll a)//这个函数是求a的逆元的函数
{
    return power(a,mod-2);//运用费马小定理求解逆元
}
ll c(ll a,ll b)//这个函数求的是C(a,b)组合数%mod的,这个函数可以求当a,b很小时组合数,当a,b很大时,我们通过Lucas定理,将a,b变小,然后运用这个方法求解
{
    return lc[a]*inv(lc[b])%mod*inv(lc[a-b])%mod;//运用公式求解C(a,b)%mod
}
ll lucas(ll a,ll b)//运用Lucas定理求解当a,b很大时的组合数%mod
{
    if(a<mod&&b<mod)
        return c(a,b);
    return lucas(a/mod,b/mod)*c(a%mod,b%mod)%mod;
}
ll modmul(ll a,ll b)//这个函数是快速积取模,累乘上每次求出来的组合数,然后取模
{
    ll ans=0;
    while (b)
    {
        if(b%2)
            ans=(ans+a)%mod;
        a=(a+a)%mod;
        b=b>>1;
    }
    return ans;
}
int main()
{
    jiecheng();//求出可能需要的数的阶乘模
    while (~scanf("%lld",&n))
    {
        ll sum=0;
        ll number;
        ll sumz=1;
        while (n--)
        {
            scanf("%lld",&number);
            sum+=number;
            sumz=modmul(sumz,lucas(sum,number));//累乘上结果
        }
        printf("%d\n",sumz);
    }
}

​

收获:

一:知道了快速积取模和快速幂取模,在可能出现大数的情况下很好用,避免出现大数。

二:知道了Lucas定理求组合数(a,b很大时用)和费马小定理求逆元,将除法取模转化为乘法取模。

三:scanf输入比cin输入快10倍   很有用!!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值