zoukankan      html  css  js  c++  java
  • 关于$NOIP2017$的题目讲解

    关于(NOIP2017)的题目讲解

    1.小凯的疑惑

    题目描述:

    小凯手中有两种面值的金币,两种面值均为正整数且彼此互素。每种金币小凯都有 无数个。在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的。现在小 凯想知道在无法准确支付的物品中,最贵的价值是多少金币?注意:输入数据保证存在 小凯无法准确支付的商品。

    讲解:

    其实这道题目可以使用很简单的方法做出来。大家也知道就是(A * B - A - B)。那么这个解到底是怎么推出来的呢?
    首先(A = B)的情况不用说。那不妨设(A < B)
    假设(A)取了(X)张,(B)取了(Y)张,那么总钱数就是(A imes Y + B imes Y = K)
    我们要找出第一个使得(A imes Y + B imes Y = K)无解的情况。
    根据欧几里德算法我们知道只有满足(gcd(A, B) ~ | ~ (A imes X + B imes Y)),才有可能使上式有解。因为题目保证(A, B)互素,那么(gcd(A, B) = 1),自然满足条件。

    假设找到一组((X_0, Y_0))满足(A imes X_0 + B imes Y_0 = 1),得到(A imes X_0 imes K+ B imes Y_0 imes K = K),它的其中一个解为((X_0 imes K, Y_0 imes K)),所以我们得到所有的解是((KX_0 + frac{Bt}{gcd(A, B)},KY_0 + frac{At}{gcd(A, B)}) = (KX_0 + Bt,KY_0 + At))

    在这里我们发现(X+0)或者(Y_0)一定有一项是(leq 0)的,不然绝对构不成式子(A imes X_0 + B imes Y_0 = 1)。假设(K = A imes B),那么满足式子的(X, Y)一定有一项是小于等于(0)的,这与题意相悖,那么我们得到(A imes Y + B imes Y = A imes B)无解。

    然后我们按照上面的思路依然可以推得(A imes Y + B imes Y = A imes B + C)((C > 0))是一定有解的。所以(K)便是最大的凑不出来的钱数。这是必须要拿的情况,然而我们还可以不拿,所以总钱数就是(X imes T - X - Y)

    当然这种方法可行,但是我们发现这个题所给的数据范围(1 leq A, B leq 10^9)并不是只有(O(1))才能过,(O(log))级别的算法也不会超时。所以我们试图扩展欧几里德

    如果我们事先假设(K)为最大的不合法的数,那么(K + 1 = K')就一定合法,那么就有(A imes X + B imes Y = K')一定成立,并且(A imes X_1 + B imes Y_1 = K)一定不成立。那么我们就要想办法使得最大的(X_{1max} < 0),并且最大的(Y_{1max} < 0)

    那么得到最后的(K)就是(A (X_{1max} - 1 ) + B(Y_{1max} - 1))。这个式子很显然是可以用扩欧求出的。

    2.逛公园

    题目描述 :

    策策同学特别喜欢逛公园。公园可以看成一张NN个点MM条边构成的有向图,且没有 自环和重边。其中1号点是公园的入口,NN号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。

    策策每天都会去逛公园,他总是从1号点进去,从NN号点出来。

    策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果1号点 到NN号点的最短路长为dd,那么策策只会喜欢长度不超过d + Kd+K的路线。

    策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?

    为避免输出过大,答案对PP取模。

    如果有无穷多条合法的路线,请输出-1−1。

    讲解 :

    首先前(30)(K = 0)的情况自然很好做,我们直接求一个最短路计数就好了。具体过程不再细讲。(代码现场打的...)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    using namespace std;
    
    typedef long long LL ;
    const int MAXN = 1200 ;
    const int Inf = 0x7fffffff ;
    
    inline Read() {
        int X = 0, F = 1 ; char ch = getchar() ;
        while (ch > '9' || ch < '0') F = (ch == '-' ? - 1 : 1), ch = getchar() ;
        while (ch >= '0' && ch <= '9') X = (X << 1) + (X << 3) + (ch ^ 48), ch = getchar() ;
        return X * F;
    }
    
    int N, M, Tot, Head[MAXN], D[MAXN], Ans[MAXN] ;
    queue<int> Q ; bool Inque[MAXN] ;
    
    void ADD(int F, int T, int L) {
        E[++ Tot].F = F, E[++ Tot].T = T, E[++ Tot].L = L ;
        E[Tot].Next = H[F], H[F] = Tot ;
    }
    inline int SPFA() {
        for (int i = 1 ; i <= N ; i ++) D[i] = Inf ;
        memset(Inque, 0, sizeof(Inque)) ;
        memset(Ans, 1, sizeof(Ans)) ;
        D[1] = 0, Inque[1] = 1, Q.push(1) ;
        while (! Q.empty()) {
            int Now = Q.front() ; Q.pop(), Inque[Now] = 0 ;
            for (int i = H[Now] ; i ; i = E[i].Next) 
                if(D[E[i].T] > D[Now] + E[i].L) {
                    D[E[i].T] = D[Now] + E[i].L ;
                    Ans[E[i].T] = Ans[Now] ;
                    if(! Inque[E[i].T]) Inque[E[i].T], Q.push(E[i].T) ;
                }   else if(D[E[i].T] == D[Now] + E[i].L)
                    Ans[E[i].T] += Ans[Now] ;
        }
    }
    
    int main() {
        N = Read(), M = Read() ;
        for (int i = 1 ; i <= M ; i ++) {
            int X = Read(), Y = Read(), Z = Read() ;
            ADD(X, Y, L) ;
        }   SPFA(1) ;
        if(D[N] == Inf) {
            puts("No answer") ; return 0 ;
        }  printf("%d %d", D[N], Ans[N]) ;
        return 0 ;
    }
    

    那么接下来考虑满分做法。

    很显然我们要先求出最短路,然后题目说了不超过(D[N] + K)的路径都可以被接受,也就是说题目可以允许走”冤枉路“。而这个允许的冤枉路的总长度也就是(K)

    每一次我们从(A)走到(B)点的时候所消耗的冤枉路的容量就是(Len[A][B] - D[A][B])。那么我们就可以以这个(K)的容量的余额为限制进行搜索。

    在此之前我们需要特判两个情况:永远走不到的点和无限的方案数。

    第一个是什么意思呢?其实就是说从这个点开始永远走不到(N)号节点,那么我们就可以将它标记,在搜索的时侯直接跳过。就比如下图的(2)号节点就一定永远不会走到,那么我们就将它排除。

    Pic1

    具体来说,我们将整个图反过来建,然后从(5)(1)搜索,途中经过的所有的点肯定都是有机会到达的,而至于(3)肯定是搜索不到的。

    void Spfa2(){
        while(! Q.empty()) Q.pop() ;
        Flag[N] = 1 ; Q.push(N) ;
        while(! Q.empty()){
            int Now = Q.front() ; Q.pop() 	;
            for( int i = H2[Now]; i; i = E2[i].Next)
                if(! Flag[E2[i].T])
                    Flag[E2[i].T] = 1, Q.push(E2[i].T) ;
        }
    }
    

    至于无限的方案数,情况在这个题里面只有一种:零环。于是当我们搜索到图中存在零环,那么我们就直接输出(- 1)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #define MEM(Now, K) memset(Now, K, sizeof(Now))
    #define MAXN 100010
    #define MAXM 200010
    #define RE register
    #define Inf 0x7ffffff
    #define LL long long
    using namespace std ;
    int Read(){
        int X = 0 ; char ch = getchar() ;
        while(ch > '9' || ch < '0') ch = getchar() ;
        while(ch >= '0' && ch <= '9')
        X = (X << 1) + (X << 3) + (ch ^ 48), ch = getchar() ;
        return X ;
    }
    int N, M, K, P, H[MAXN], Tot ;
    int V[MAXN][55], U[MAXN][55] ;
    struct NODE{
        int F, T, L, Next ;
    }E[MAXM << 1], E2[MAXM << 1] ;
    inline void Add(int F, int T, int L){
        E[++ Tot].F = F, E[Tot].T = T, E[Tot].L = L ;
        E[Tot].Next = H[F], H[F] = Tot ; return  ;
    }
    int H2[MAXN], Tot2 ;
    inline void Add2(int F, int T, int L){
        E2[++ Tot2].F = F, E2[Tot2].T = T, E2[Tot2].L = L ;
        E2[Tot2].Next = H2[F], H2[F] = Tot2 ; return   ;
    }
    int D[MAXN], I[MAXN] ;
    queue<int> Q ;
    bool Flag[MAXN] ;
    void Spfa(){
        for( int i = 1; i <= N; i ++)
            D[i] = Inf ;
        D[1] = 0, Q.push(1) ;	
        while(! Q.empty()){
            int Now = Q.front() ; Q.pop() ;
            for( int i = H[Now]; i; i = E[i].Next){
                int V = E[i].T ;
                if(D[V] > D[Now] + E[i].L){
                    D[V] = D[Now] + E[i].L ;
                    Q.push(V) ;
                }
            }
        }
    }
    void Spfa2(){
        while(! Q.empty()) Q.pop() ;
        Flag[N] = 1 ; Q.push(N) ;
        while(! Q.empty()){
            int Now = Q.front() ; Q.pop() 	;
            for( int i = H2[Now]; i; i = E2[i].Next)
                if(! Flag[E2[i].T])
                    Flag[E2[i].T] = 1, Q.push(E2[i].T) ;
        }
    }
    int Dfs(int X, int Y){
        if(Y < 0) return 0 ;
        if(V[X][Y] == 1) return - Inf ;
        if(U[X][Y] != - 1) return U[X][Y] ;
        V[X][Y] = 1 ; int Ans = 0 ;
        if(X == N) Ans ++ ;
        for( int i = H[X]; i; i = E[i].Next){
            if(Flag[E[i].T] == 0) continue ;
            int C = Dfs(E[i].T, Y - E[i].L + D[E[i].T] - D[X]) ;
            if(C == - Inf) return C ;
            else Ans = (Ans + C) % P ;
        }
        U[X][Y] = Ans % P ;
        V[X][Y] = 0 ;
        return Ans ;
    }
    int main(){
        int T = Read() ;
        while(T --){
            MEM(D, 127), MEM(I, 0), MEM(V, 0), MEM(H, 0) ; Tot = 0 ;
            MEM(E, 0), MEM(E2, 0), MEM(Flag, 0), MEM(H2, 0) ; Tot2 = 0 ;
            N = Read(), M = Read(), K = Read(), P = Read() ;
            for( int i = 0; i <= N; i ++)
                for(int j = 0; j <= 55; j ++)
                    U[i][j] = - 1 ; 
            for( int i = 1; i <= M; i ++){
                int X = Read(), Y = Read(), Z = Read() ;
                Add2(Y, X, Z) ; Add(X, Y, Z) ;
            }
            Spfa() ;  Spfa2() ; 
            int Ans = Dfs(1, K) ;
            if(Ans == - Inf) puts("-1") ;
            else printf("%d
    ", Ans) ;
        }
        return 0 ;
    }
    

    3.奶酪

    题目描述:

    现有一块大奶酪,它的高度为(h),它的长度和宽度我们可以认为是无限大的,奶酪 中间有许多 半径相同 的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中, 奶酪的下表面为(z = 0),奶酪的上表面为(z = h)

    现在,奶酪的下表面有一只小老鼠(Jerry),它知道奶酪中所有空洞的球心所在的坐 标。如果两个空洞相切或是相交,则(Jerry)可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,(Jerry)则可以从奶酪下表面跑进空洞;如果 一个空洞与上表面相切或是相交,(Jerry)则可以从空洞跑到奶酪上表面。

    位于奶酪下表面的 Jerry 想知道,在 不破坏奶酪 的情况下,能否利用已有的空洞跑 到奶酪的上表面去?

    空间内两点(P_1(x_1,y_1,z_1))(P2(x_2,y_2,z_2))的距离公式如下:

    [Dist(P_1, P_2) = sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2 + (z_1 - z_2)^2} ]

    讲解:

    这大概是(NOIP2017)最简单的一道题了。(傻*时间复杂度)

    当然你是可以搜索的。但是在这里使用的方法是并查集。

    考虑将所有的相交或者相切的空洞并查集起来,然后最后判断是不是存在一个集合既与上表面相交或相切,又与下表面相交或相切。输出结果。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    typedef long long LL ;
    const int MAXN = 1000101 ;
    const int Inf = 0x7fffffff ;
    long long X[MAXN], Y[MAXN], Z[MAXN], F[MAXN] ;
    long long F1[MAXN],Tot1, F2[MAXN],. Tot2 ;
    
    inline long long Read() {
        long long X = 0 ; char ch = getchar() ;
        while (ch > '9' || ch < '0') ch = getchar() ;
        while (ch >= '0' && ch <= '9')
            X = (X << 1) + (X << 3) + (ch ^ 48), ch = getchar() ;
        return X ;
    }
    
    inline long long POW(LL X) {
        return X * X ;
    }
    inline long long Find(int Now) {
        if (Now == F[Now]) return F[Now] ;
        else return F[Now] = Find (F[Now]) ;
    }
    ll dis(ll x,ll y,ll z,ll x1,ll y1,ll z1)
    { return dou(x-x1)+dou(y-y1)+dou(z-z1);}
    inline long long Dist(LL X, LL Y, LL Z, LL X1, LL Y1, LL Z1) {
        return POW(X - X1) + POW(Y - Y1) + POW(Z - Z1) ;
    }
    int f1[MAXN],f2[MAXN];
    inline void Union(int X, int Y) {
         long long R1 = Find(X), R2 = Find(Y) ;
         if(R1 != R2) F[R1] = R2 ;
    }
    int main() {
        int t,n,h;
        ll r;
        long long T = Read() ;
        while (T --) {
            long long N = Read(), H = Read(), R = Read() ;
            long long Tot1 = Tot2 = 0 ;
            for (int i = 1 ; i <= N ; i ++) F[i] = i ;
            for (int i = 1 ; i <= N ; i ++) {
                long long X[i] = Read(), Y[i] = Read(), Z[i] = Read() ;
                if (Z[i] + R >= H) F1[++ Tot1] = i ;
                if (Z[i] - R <= 0) F2[++ Tot2] = i ;
                for (int j = 1 ; j <= i ; j ++) 
                    if (Dist(X[i], Y[i], Z[i], X[j], Y[j], Z[j]) <= 4 * R * R)
                        Union(i, j) ;
            }   long long S = 0 ;
            for (int i = 1 ; i <= Tot1 ; i ++) {
                for (int j = 1 ; j <= Tot2 ; j ++)
                    if (Find(F1[i]) == Find(F2[j])) {
                        S = 1 ; break ;
                    }
                if (S == 1) break ;
            }
            if(S == 1) puts("Yes") ;
            else puts("No") ;
        }
    }
    
  • 相关阅读:
    PAT Basic 1077 互评成绩计算 (20 分)
    PAT Basic 1055 集体照 (25 分)
    PAT Basic 1059 C语言竞赛 (20 分)
    PAT Basic 1072 开学寄语 (20 分)
    PAT Basic 1049 数列的片段和 (20 分)
    蓝桥杯BASIC-13 数列排序
    蓝桥杯入门——3.序列求和
    蓝桥杯入门——2.圆的面积
    蓝桥杯入门——1.Fibonacci数列
    树的总结(遍历,BST,AVL原型,堆,练习题)
  • 原文地址:https://www.cnblogs.com/sue_shallow/p/NOIP2017.html
Copyright © 2011-2022 走看看