1. 程式人生 > 其它 >【Coel.解題報告】【推柿子的基本思路】[NOI2002] 荒島野人

【Coel.解題報告】【推柿子的基本思路】[NOI2002] 荒島野人

題前碎語

還有13天就要期考了,可我還是在機房頹題目。
其實我本來不是很想頹的,可是這道題對於學習擴歐來說很重要,所以還是放進來一下。

題目簡介

洛谷傳送門

題目描述

克里特島以野人群居而著稱。島上有排列成環行的 \(m\) 個山洞。這些山洞順時針編號為 \(1,2,\dots ,m\) 。島上住著 \(n\) 個野人,一開始依次住在山洞 \(C_1,C_2,\dots ,C_n\)中,以後每年,第 \(i\) 個野人會沿順時針向前走 \(P_i\) 個洞住下來。

每個野人 \(i\) 有一個壽命值 \(L_i\) ,即生存的年數。

下面四幅圖描述了一個有 \(6\) 個山洞,住有三個野人的島上前四年的情況。三個野人初始的洞穴編號依次為 \(1,2,3\)

;每年要走過的洞穴數依次為 \(3,7,2\);壽命值依次為 \(4,3,1\)

奇怪的是,雖然野人有很多,但沒有任何兩個野人在有生之年處在同一個山洞中,使得小島一直保持和平與寧靜,這讓科學家們很是驚奇。他們想知道,至少有多少個山洞,才能維持島上的和平呢?

輸入格式

\(1\) 行為一個整數 \(n(1\leq n\leq 15)\),即野人的數目。

\(2\) 行到第 \(N+1\) 每行為三個整數 \(C_i, P_i, L_i (1\leq C_i,P_i \leq 100, 0\leq L_i\leq 10^6 )\),表示每個野人所住的初始洞穴編號,每年走過的洞穴數及壽命值。

輸出格式

僅包含一個數 \(m\),即最少可能的山洞數。輸入資料保證有解,且 \(m\) 不大於\(10^6\)


解題過程

這裡描述一下全過程。
假設兩個野人\(i,j\)\(x\)年之後會走到一起,則在\(x\)年後,他們的位置分別是
\((C_i+xP_i) \mod m\)\((C_j+xP_j) \mod m\)
也就是說,存在一個數\(x \leq (min(L_i,L_j))\),滿足

\[C_i+xP_i \equiv C_j+xP_j \pmod m \]

那麼,我們的目標就是找到讓這\(n^2\)個同餘方程無解的\(m\)取值。
觀察到資料範圍很小而且\(m\)的範圍已知,考慮暴力列舉\(m\)

的取值,並用擴歐求解。
程式碼如下:

#include <cstdio>
#include <cctype>
#include <iostream>
#include <cmath>
using namespace std;

const int maxn = 20;
int n, m, c[maxn], p[maxn], l[maxn];

inline int read() {
	
	int x = 0, f = 1;
	char ch = getchar();
	
	while(!isdigit(ch)) {
		if(ch == '-') f = -1;
		ch = getchar(); 
	}
	
	while(isdigit(ch)) {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	
	return x * f;
}

inline int exgcd(int a, int b, int &x, int &y) {
	
	if(b == 0) {
		
		x = 1, y = 0;
		return a;
	}
	
	int r = exgcd(b, a % b, x, y), tp = x;
	x = y, y = tp - a / b * y;
	
	return r;
}

inline bool solve(int m) {
	
	for(int i = 1; i <= n; i++)
		for(int j = i + 1; j <= n; j++) {
			
			int a = p[i] - p[j], b = m, x, y;
			int gcd = exgcd(a, b, x, y), len = c[j] - c[i];
			
			if(len % gcd != 0) continue;
			a /= gcd, b /= gcd, len /= gcd;
			b = abs(b);
			x = (x * len % b + b) % b;
			if(x <= l[i] && x <= l[j]) return false;//方程有解,說明m不合題意
		}
			
	return true;
}
int main() {
	
	#ifndef ONLINE_JUDGE
	freopen("savage.in", "r", stdin);
	freopen("savage.out", "w", stdout);
	#endif
	
	n = read();
	for(int i = 1; i <= n; i++) {
		c[i] = read(), p[i] = read(), l[i] = read();
		m = max(m, c[i]);//保證m大於等於任意一個c[i]
	}
	
	while(!solve(m)) m++;
	
	printf("%d", m);
	
	return 0;
}

題後閒話

20年的時候這題還是紫題,21年變成藍題了
雖然這題本來就很水