• 关于$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") ;
        }
    }
    
  • 相关阅读:
    三种回归算法及其优缺点
    线性回归于逻辑回归的区别
    置信度与置信区间
    js表单验证是否为合法数据
    unity变换游戏对象
    Unity克隆物体
    Unity创建游戏对象_位置,大小,旋转
    Unity GUI获取玩家名字并在控制台输出
    算法竞赛入门经典_暴力求解法
    java Swing组件的对齐问题2
  • 原文地址:https://www.cnblogs.com/sue_shallow/p/NOIP2017.html
Copyright © 2020-2023  润新知