fireworks----乘法逆元求组合数(2017山东ACM-ICPC省赛)

Problem Description

Hmz likes to play fireworks, especially when they are put regularly.
Now he puts some fireworks in a line. This time he put a trigger on each firework. With that trigger, each firework will explode and split into two parts per second, which means if a firework is currently in position x, then in next second one part will be in position x−1 and one in x+1. They can continue spliting without limits, as Hmz likes.
Now there are n fireworks on the number axis. Hmz wants to know after T seconds, how many fireworks are there in position w?
这里写图片描述

Input

Input contains multiple test cases.
For each test case:

The first line contains 3 integers n,T,w(n,T,|w|≤10^5)
In next n lines, each line contains two integers xi and ci, indicating there are ci fireworks in position xi at the beginning(ci,|xi|≤10^5).

Output

For each test case, you should output the answer MOD 1000000007.
Example Input

1 2 0
2 2
2 2 2
0 3
1 2

Example Output

2
3
题意:一个烟花一秒后变成两个烟花分别在上一个烟花位置左右的一个单位。给你多组数据,第一行为三个数n,T,w分别代表有几个位置有烟花,以及目标位置,下面有n行每一行有两个数x[i],c[i]代表的是烟花的位置以及烟花的个数,让你求在t秒后目标位置一共有多少烟花。

    -3  -2  -1  0  1   2   3   4   5  6  7 
    0.                             1
    1.                           1    1
    2.                        1    2     1
    3.                      1    3    3    1
    4.                    1   4     6    4    1
    5.                  1   5   10    10   5    1
                     ........               .........                                  

画出来发现是一个杨辉三角。杨辉三角有两个重要的性质,1.除了边缘的1其他的数都是肩膀的和,2.第n层(n>=0)的第m(m>=0)个数为排列组合数C(n,m)。那么想要求解目标位置的烟花的个数就相当于求解排列组合C(n,m)。求排列数有很多方法其中最简单的就是暴力求解(忽略不计)较慢,其次便是卢卡斯定理和Pascal公式打表,还有就是用逆元的方法去做。(目前我就知道这三种方法了)。下面就讲讲这三种方法。
1.Pascal公式打表。
对于1<=k<=n-1的正整数来说
有以下公式:C(n,k)=C(n-1,k-1)+C(n-1,k);
C(n,n)=C(n,0)=1;

取二维数组 tC[][] ,初始化 tC[0][0] = 1; 打表即可。代码最简单,如下:

        const int maxn(1005), mod(100003);
    int tC[maxn][maxn]; //tC 表示 table of C
    inline int C(int n, int k)
    {
        if(k > n) return 0;
        return tC[n][k];
    }
    void calcC(int n)
    {
        for(int i = 0; i < n; i++)
        {
            tC[i][0] = 1;
            for(int j = 1; j < i; j++)
                tC[i][j] = (C(i - 1, j - 1) + C(i - 1, j)) % mod;
            tC[i][i] = 1;
        }
    }

当然我们知道 C(n,k)=C(n,n-k),所以上面的代码有很多空间和时间的浪费。可以将 tC[][] 二维数组转化为一维数组存储,同时,当 j>i/2时终止第二层循环,新代码如下:

    const int maxn(10005), mod(100003);
    int tC[maxn * maxn]; //tC 表示 table of C
    inline int loc(int n, int k) // C(n, k)返回在一维数组中的位置
    {
        int locate = (1 + (n >> 1)) * (n >> 1); // (n >> 1) 等价于 (n / 2)
        locate += k;
        locate += (n & 1) ? (n + 1) >> 1 : 0; // (n & 1) 判断n是否为奇数
        return locate;
    }
    inline int C(int n, int k)
    {
        if(k > n) return 0;
        k = min(n - k, k);
        return tC[loc(n, k)];
    }
    void calcC(int n)
    {
        for(int i = 0; i < n; i++)
        {
            tC[loc(i, 0)] = 1;
            for(int j = 1, e = i >> 1; j <= e; j++)
                tC[loc(i, j)] = C(i - 1, j) + C(i - 1, j - 1);
        }
    }

2 .卢卡斯定理
相关证明及其代码
3 .乘法逆元求组合数。
相关证明及其代码
有了上面的知识基础,下面就能着手解决这个问题了。首先一步是要判断在t秒后目标位置是否能得到烟花需要满足以下条件:
t%2==|w-x[i]|%2&&x[i]-t<=w<=x[i]+t;
下一步就是要把题目中的相关条件转化为排列组合中的n和m;
t=n;然后找m,自己在纸上模拟一遍发现m=(t+1)/2+(|w-x[i]|)/2;
下面问题就迎刃而解了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string>
using namespace std;
typedef long long LL;
const LL maxn(100005), mod(1e9 + 7);
LL Jc[maxn];

void calJc()    ///求maxn以内的数的阶乘
{
    Jc[0] = Jc[1] = 1;
    for(LL i = 2; i < maxn; i++)
        Jc[i] = Jc[i - 1] * i % mod;
}
///拓展欧几里得算法求逆元
void exgcd(LL a, LL b, LL &x, LL &y)    ///拓展欧几里得算法
{
    if(!b) x = 1, y = 0;
    else
    {
        exgcd(b, a % b, y, x);
        y -= x * (a / b);
    }
}

LL niYuan(LL a, LL b)   //求a对b取模的逆元
{
    LL x, y;
    exgcd(a, b, x, y);
    return (x + b) % b;
}

/*//费马小定理求逆元
LL pow(LL a, LL n, LL p)    //快速幂 a^n % p
{
    LL ans = 1;
    while(n)
    {
        if(n & 1) ans = ans * a % p;
        a = a * a % p;
        n >>= 1;
    }
    return ans;
}

LL niYuan(LL a, LL b)   //费马小定理求逆元
{
    return pow(a, b - 2, b);
}*/

LL C(LL a, LL b)    //计算C(a, b)
{
    return Jc[a] * niYuan(Jc[b], mod) % mod
           * niYuan(Jc[a - b], mod) % mod;
}
int main()
{
    LL n,t,w;
    LL ans;
    calJc();///初始化求1~n的各个阶乘
    while(scanf("%lld%lld%lld",&n,&t,&w)!=EOF)
    {
        int x,c;
        int xx;
        ans = 0;
        for(int i=0; i<n; i++)
        {
            scanf("%d%d",&x,&c);
            xx = abs(w - x);
            if(xx%2 && t%2 && t >= xx)///烟花能在目标位置的条件
                ans += C(t,(t+1)/2 + xx/2) *c% mod;
            else if(xx%2==0 && t%2==0 && t >= xx)
                ans += C(t,xx/2+t/2)*c % mod;
            ans %= mod;
        }

        printf("%lld\n",ans);
    }
    return 0;
}

本题心得:逆元这个知识以前也看过但总是很畏惧它看的也都事实而非。静下心认真的去看还是能看懂的。就这个题而言也是一道中规中矩的题目只要能理解题意将其抽象化,以及将题中的各个变量对应起来,根据求组合数的知识来求解,这其中的任何一个环节都是缺一不可。这就要我们在平时的训练中更加的仔细。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值