1. 程式人生 > 其它 >[NOI2022]荒島野人(exgcd)

[NOI2022]荒島野人(exgcd)

現在看來一道很\(exgcd\)在當年竟是一道\(NOI\)題目。

原題面

題目描述

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

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

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

壽命值依次為\(4,3,1\)

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

輸入格式

\(n\)行為一個整數\(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\)

樣例輸入

3
1 3 4
2 7 3
3 2 1

樣例輸出

6

說明/提示

\(1\le N\le15\)\(1\leq C_i,P_i\leq 100\)\(0\leq L_i\leq 10^6\)

保證\(M\le10^6\)

題解

我們從小到大列舉答案,我們再列舉兩個不同的野人\(i,j\),我們判斷\(i,j\)在壽命值範圍內能否相遇,能就說明列舉的答案不合法,如果所有\(i,j\)都不能相遇,那就說明合法,輸出答案結束程式。

如何判斷

我們現在的問題是如何判斷他們能否相遇。

\(\forall i,j\)\(a=P_i-P_j\)即一年\(i\)\(j\)多走的步數,\(x\)

天后相遇,\(b=C_j-C_i\)\(j\)初始的位置在\(i\)前面多少單位。我們的判斷變為判斷\(ax\equiv b(\mod M)\),我們轉化一下可得\(ax+My=b\)的整數解。

這樣就可以用擴充套件歐幾里德來解決了,時間複雜度是\(O(MN^2\log M)\)可以通過本體。

code

#include<bits/stdc++.h>
#define fre(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout);
using namespace std;
int n,c[25],p[25],l[25],w;
int max(int a,int b){return (a>b)?a:b;}
int exgcd(int a,int b,int &x,int &y)
{
	if(b==0)
	{
		x=1;y=0;
		return a;
	}
	int d=exgcd(b,a%b,y,x);
	y=y-a/b*x;
	return d;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&c[i],&p[i],&l[i]);
		w=max(w,c[i]);
	}
	for(int d,x,y,a,b;;w++)
	{
		bool flag=0;
		for(int i=1;i<n;i++)
		{
			for(int j=i+1;j<=n;j++)
			{
				a=p[i]-p[j];b=c[j]-c[i];
				if(a<0)a=-a,b=-b;
				d=exgcd(a,w,x,y);
				int m=w/d;
				x=(x*b/d%m+m)%m;
				if(!x)x+=m;
				if(b%d==0&&x<=l[i]&&x<=l[j])flag=1;
				if(flag)break;
			}
			if(flag)break;
		}
		if(!flag){printf("%d",w);return 0;}
	}
    return 0;
}