首页 > 试题广场 >

CodeForces 559C Gerald and Gia

[编程题]CodeForces 559C Gerald and Gia
  • 热度指数:38 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解

Giant chess is quite common in Geraldion. We will not delve into the rules of the game, we'll just say that the game takes place on an h × w field, and it is painted in two colors, but not like in chess. Almost all cells of the field are white and only some of them are black. Currently Gerald is finishing a game of giant chess against his friend Pollard. Gerald has almost won, and the only thing he needs to win is to bring the pawn from the upper left corner of the board, where it is now standing, to the lower right corner. Gerald is so confident of victory that he became interested, in how many ways can he win?

The pawn, which Gerald has got left can go in two ways: one cell down or one cell to the right. In addition, it can not go to the black cells, otherwise the Gerald still loses. There are no other pawns or pieces left on the field, so that, according to the rules of giant chess Gerald moves his pawn until the game is over, and Pollard is just watching this process.


输入描述:

The first line of the input contains three integers: h, w, n — the sides of the board and the number of black cells (1 ≤ h, w ≤ 105, 1 ≤ n ≤ 2000).

Next n lines contain the description of black cells. The i-th of these lines contains numbers ri, ci (1 ≤ ri ≤ h, 1 ≤ ci ≤ w) — the number of the row and column of the i-th cell.

It is guaranteed that the upper left and lower right cell are white and all cells in the description are distinct.



输出描述:

Print a single line — the remainder of the number of ways to move Gerald's pawn from the upper left to the lower right corner modulo 109 + 7.

示例1

输入

3 4 2<br />2 2<br />2 3<br />100 100 3<br />15 16<br />16 15<br />99 88<br />

输出

2<br />545732279<br />
 /**
 * @author week
 * @Title: Giant_Chess
 * @ProjectName 19-0316
 * @Description: 不得不说,这道题很难。
 * 要掌握的知识点:
 * 第一部分:得到dp状态转移方程
 * 1.排列组合,从一个开始到达结束,一共C(m+n,n)种方法。
 * 2.计数类dp。
 * 3.用总方案数减去经过黑格子的方案个数,得到的就是答案。
 * 4.但是经过黑格子的路径可能经过很多个黑格子,导致重复计算。
 * 5.于是可以设dp[i]表示从起点到黑点i的合法路径个数,合法路径是说这之前不经过其他黑点。
 * 6.那么dp[i]=(起点走到点i的路径总数)-∑(dp[j]*(点j到点i的路径总数),点j在点i的左上方)
 * 第二部分:排序问题
 * 1.保证计算点i时,所有在点i左上方的点都计算过。可以通过坐标排序解决。 
 * 第三部分:如何设计C(m+n,n)
 * 0.乘法逆元
 * 1.费马小定理。
 * 2.扩展欧几里得算法。
 * 3.阶乘的乘法逆元递推公式。
 * ps:每个我都有实现,其中计算N!的逆元我用的是费马小定理。扩展欧几里得算法闲置了。
 * @date 2019/6/811:09
 */
import java.util.*;
public class Main {
    static int mod=1000000007;
    //缓存n!的值
    static long f[]=new long[200010];
    //缓存n!阶乘的乘法逆元
    static long inv[]=new long[200010];
    //dp表,含义是到达第i个黑点,有几种方法
    static long dp[]=new long[2010];
    //保存黑节点
    static class Node{
        public int x;
        public int y;
        public Node(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        init(200009);
        while (sc.hasNext()){
            int h=sc.nextInt();
            int w=sc.nextInt();
            int n=sc.nextInt();
            Node list[]=new Node[n+1];
            for(int i=0;i<n;i++){
                int x=sc.nextInt();
                int y=sc.nextInt();
                list[i]=new Node(x,y);
            }
            list[n]=new Node(h,w);
            //按x轴优先排序
            Arrays.sort(list, new Comparator<Node>() {
                @Override
                public int compare(Node o1, Node o2) {
                    if(o1.x!=o2.x) return o1.x-o2.x;
                    return o1.y-o2.y;
                }
            });

            for (int i=0;i<=n;i++)
            {
                int x1=list[i].x-1;
                int y1=list[i].y-1;
                dp[i] = C(x1,y1) ;
                for(int j = 0 ; j < i ; j++) {
                    if( list[j].x <= list[i].x && list[j].y <= list[i].y ) {
                        int x2 = x1 - list[j].x+1 ; int y2 = y1 - list[j].y+1 ;
                        dp[i] = (dp[i]-C(x2,y2)*dp[j]%mod)%mod ;
                        if( dp[i] <= 0 ) dp[i] = (dp[i]+mod)%mod;
                    }
                }

            }
            System.out.println(dp[n]);
        }
    }

    /**
     * C(m+n,n)=(m+n)!/(m!*n!)
     * @param m
     * @param n
     * @return
     */
    public static long C(int m,int n){
        if(n<0||m<0) return 0;
        long ans=f[n+m]*inv[n]%mod*inv[m]%mod;
        return ans;
    }

    /**
     * 递推公式:https://blog.csdn.net/qq_40861916/article/details/82928080
     * 因为是从后往前的,所以要先用费马小定理算出来最大的
     */
    public  static void init(int N){
        f[0]=1;
        for(int i=1;i<=N;i++)
            f[i]=f[i-1]*i%mod;
        inv[0]=1;
        inv[N]=Fermat(f[N],mod-2);
        for(int i=N-1;i>0;i--)
            inv[i]=inv[i+1]*(i+1)%mod;
    }

    /**
     * 费马小定理:当有两数a,pa,p满足gcd(a,p)=1gcd(a,p)=1时,则有ap≡a(mod p)ap≡a(mod p)。
     * 变一下形:a⋅ap−2≡1(mod p)a⋅ap−2≡1(mod p)。是不是和上面的乘法逆元的定义是相似的?
     * 所以,我们可以使用快速幂求出ap−2ap−2,即求出aa的逆元。
     * @param a 求解的数
     * @param b p-2
     * @return 乘法逆元
     */
    public static  long Fermat(long a,int b){
        long ret=1;
        while (b>0){
            if((b&1)==1){
                ret=ret*a%mod;
            }
            a=a*a%mod;
            b=b>>1;
        }
        return ret;
    }

    static class returnType{
        public int ans;
        public int x;
        public int y;
        public returnType(int ans, int x, int y) {
            this.ans = ans;
            this.x = x;
            this.y = y;
        }
    }

    /**
     *扩展欧几里得算法
     * @param a
     * @param b
     * @return ans为最小公约数,x%mod为其一个逆元,y为另外一个解,可以不关心
     *公式递推:https://blog.csdn.net/alxbaj/article/details/81121447
     */
    public  static returnType extendedEuclidean(int a,int b){
        if(b==0){
            return new returnType(a,1,0);
        }
        returnType type=extendedEuclidean(b,a%b);
        return new returnType(type.ans,type.y,type.x-a/b*type.y);
    }

}

发表于 2019-06-08 18:41:24 回复(0)