关于\(NOIP2017\)的题目讲解
1.小凯的疑惑
题目描述:
小凯手中有两种面值的金币,两种面值均为正整数且彼此互素。每种金币小凯都有 无数个。在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的。现在小 凯想知道在无法准确支付的物品中,最贵的价值是多少金币?注意:输入数据保证存在 小凯无法准确支付的商品。
讲解:
其实这道题目可以使用很简单的方法做出来。大家也知道就是\(A * B - A - B\)。那么这个解到底是怎么推出来的呢?
首先\(A = B\)的情况不用说。那不妨设\(A < B\)。
假设\(A\)取了\(X\)张,\(B\)取了\(Y\)张,那么总钱数就是\(A \times Y + B \times Y = K\)。
我们要找出第一个使得\(A \times Y + B \times Y = K\)无解的情况。
根据欧几里德算法我们知道只有满足\(gcd(A, B) ~ | ~ (A \times X + B \times Y)\),才有可能使上式有解。因为题目保证\(A, B\)互素,那么\(gcd(A, B) = 1\),自然满足条件。
假设找到一组\((X_0, Y_0)\)满足\(A \times X_0 + B \times Y_0 = 1\),得到\(A \times X_0 \times K+ B \times Y_0 \times K = K\),它的其中一个解为\((X_0 \times K, Y_0 \times 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 \times X_0 + B \times Y_0 = 1\)。假设\(K = A \times B\),那么满足式子的\(X, Y\)一定有一项是小于等于\(0\)的,这与题意相悖,那么我们得到\(A \times Y + B \times Y = A \times B\)无解。
然后我们按照上面的思路依然可以推得\(A \times Y + B \times Y = A \times B + C\)\((C > 0)\)是一定有解的。所以\(K\)便是最大的凑不出来的钱数。这是必须要拿的情况,然而我们还可以不拿,所以总钱数就是\(X \times T - X - Y\)。
当然这种方法可行,但是我们发现这个题所给的数据范围\(1 \leq A, B \leq 10^9\)并不是只有\(O(1)\)才能过,\(O(log)\)级别的算法也不会超时。所以我们试图扩展欧几里德。
如果我们事先假设\(K\)为最大的不合法的数,那么\(K + 1 = K'\)就一定合法,那么就有\(A \times X + B \times Y = K'\)一定成立,并且\(A \times X_1 + B \times 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\)号节点就一定永远不会走到,那么我们就将它排除。
具体来说,我们将整个图反过来建,然后从\(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\n", 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") ;
}
}