【CodeForces】559C Gerald and Giant Chess 國際象棋 解題思路(Java)

參考了:
[1].乘法逆元詳解【費馬小定理+擴展歐幾里得算法】
[2].逆元的三種求法 (費馬小定理,擴展歐幾里得,遞推求階乘逆元)
[3].【Codeforces Round #313 (Div. 1) C】 CodeForces 559C Gerald and Giant Chess
不得不說,這道題很難。
要掌握的知識點:
第一部分:得到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!的逆元我用的是費馬小定理。擴展歐幾里得算法閒置了。
/**
 * @author week
 * @Title: Giant_Chess
 * @ProjectName 19-0316
 * @Description:
 * @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);
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章