关于$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") ;
    }
}
原文地址:https://www.cnblogs.com/sue_shallow/p/NOIP2017.html