LINUX.ORG.RU

Задача про прямоугольный треугольник от Яндекса

 ,


1

3

Привет, лор!

Я решил попрактиковаться в программировании и наткнулся на задачу от Яндекса про прямоугольные треугольники. Я ее почти решил, только один из тестов не проходит, никак не пойму что не так.

На стандартный поток ввода подаётся три целых положительных числа — стороны треугольника. Числа не превосходят 30000.

Формат вывода Если полученный треугольник является прямоугольным, напечатайте YES. Если треугольник не является прямоугольным, напечатайте NO. Если с заданными сторонами невозможно построить треугольник, напечатайте UNDEFINED.

Вот как я рассудил: надо найти максимальную и меньшие стороны треугольника. Если максимальная больше или равна сумме меньших, то такой треугольник нельзя построить. А если все ок, то сравниваем квадраты меньших сторон и гипотенузы. Все хорошо ровно до теста №24. В какой-то момент я отчаялся и заменил мое сравнение на тупой перебор сумм сторон и все заработало. Теперь никак не могу понять где я был не прав в своей первой гипотезе.

Вот весь код:

#include <iostream>
#include <algorithm>

static int undefined() {
    std::cout << "UNDEFINED" << std::endl;
    return 0;
}

int main()
{
    int32_t l[3];
    for(int i = 0; i < 3; i++) {
        std::cin >> l[i];
        
        if(l[i] < 1) {
            return undefined();
        }
    }
    
    int32_t a, b, h;
    h = std::max(l[0], l[1]); // большее из первых двух чисел побудет пока гипотенузой
    a = std::min(l[0], l[1]); // а меньшее - точно не гипотенуза
    b = std::min(h, l[2]);    // то же самое
    h = std::max(h, l[2]);    // h теперь - наибольшая сторона

    if(h >= a + b) {
        return undefined();
    }

    // если заменить условие выше на вот это, то все проходит:
//    return l[0] + l[1] > l[2] and
//           l[1] + l[2] > l[0] and
//           l[0] + l[2] > l[1];

    
    if(a * a + b * b == h * h ) {
        std::cout << "YES";
    }
    else {
        std::cout << "NO";
    }
    
    std::cout << std::endl;

    return 0;
}

Ответ на: комментарий от magictree

Еще было бы неплохо переписать условия двух вариантов одинаково - в одном случае выглядит: если треугольник не существует, вернуть undefined(), во втором случае рассматривается видимо обратное, а возвращается вообще bool.

Яндекс хотя бы пишет, что ожидалось и что получилось?

chkalov
()
Ответ на: комментарий от ya-betmen

эквивалентен. В закомментированном коде проверяется, что длина стороны меньше суммы двух других, а в рабочем - ровно противоположное этому утверждение, что длина стороны НЕ меньше суммы двух других.

biglpfan
()
Ответ на: комментарий от bugfixer

не, это я тоже пробовал. это скорее всего чтобы я не парился при переполнении интов при возведении в квадрат.

в общем, завтра у меня будет доступ к нормальной системе, а не онлайн-компилятору и я попробую брутфорсом найти разницу.

magictree
() автор топика
Ответ на: комментарий от magictree

это скорее всего чтобы я не парился при переполнении интов при возведении в квадрат.

Вам там даже битик оставили чтобы и сумма квадратов не переполнялась. Но будет очень интересно что не так - сходу не очевидно.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)

Я не вижу явной ошибки в твоём решении. Более того, я попробовал скормить этот код проверялке Яндекса, и она написала, что всё верно. Никаких ошибок.

Похоже, ты наткнулся на какой-то глюк.

i-rinat ★★★★★
()
Ответ на: комментарий от bugfixer

Хм, растягиваем 3, 4, 5 вдвое - получаем гипотенузу 10. Вы явно чего то недоговариваете :)

Хм…и ведь действительно недоговорил :-) хотя пока всё гарантийно влезает в int32_t это скорее overengineering

MKuznetsov ★★★★★
()

Если максимальная больше или равна сумме меньших, то такой треугольник

Вырожденный треугольник, у которого 1 угол это 180 градусов, а другие 0 не попадает в это условие, хотя формально это треукольник.

Aswed ★★★★★
()

Код должен быть максимально простым, так проще недопускать ошибок.

Поэтому даже в мелочах лучше следовать правилам хорошего тона, называть переменные по смыслу, создавать отдельные функции при переходе к другим уровням абстракции, ну и не использоваться шаблоны, когда они не нужны.

Я бы написал так (проверку на неположительные значения я не сделал, т.к. в условии сказано, что значения положительны).

#include <iostream>


static void undefined() {
    std::cout << "UNDEFINED" << std::endl;
}


static void yes() {
    std::cout << "YES" << std::endl;
}


static void no() {
    std::cout << "NO" << std::endl;
}


void right_triangle_test() {
    int32_t length[3];
    for(int edge = 0; edge < 3; edge++) {
        std::cin >> length[edge];
    }

    if(length[0] >= length[1] + length[2]) return undefined();
    if(length[1] >= length[0] + length[2]) return undefined();
    if(length[2] >= length[0] + length[1]) return undefined();
    
    int32_t square[3];
    for(int edge = 0; edge < 3; edge++) {
        square[edge] = length[edge] * length[edge];
    }    

    if(square[0] == square[1] + square[2]) return yes();
    if(square[1] == square[0] + square[2]) return yes();
    if(square[2] == square[0] + square[1]) return yes();

    return no();
}


int main() {
    right_triangle_test();
    return 0;
}
soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 2)
Ответ на: комментарий от Aswed

Ну, достаточно убрать или ровна из условия и будет попадать. Прямоугольные под фильтр теоремы Пифагора проскочат. ТС, вроде как, так и сделал, но тест не прошёл. Я тоже, не видя теста 24, как-то не вижу явных логических ошибок.

SkyMaverick ★★★★★
()
Ответ на: комментарий от soomrack

Код должен быть максимально простым, так проще недопускать ошибок.

template<typename T>
int stdPif(T a,T b,T c)
{
	if (a>b) std::swap(a,b);
	if (b>c) std::swap(b,c); // теперь с-потенциальная гипотенуза
	if (a<1 || b<1 || a/2+b/2>c/2 ) return -1;	// не треугольник 
	// если не проверять переполнения
	return std::sqrt(a*a+b*b)==c; // true-треугольник,false-нет
}
MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)
Ответ на: комментарий от MKuznetsov

fix:

template<typename T>
int stdPif(T a,T b,T c)
{
	if (a>b) std::swap(a,b);
	if (b>c) std::swap(b,c);
	if (a<1 || b<1 || a/2+b/2>c/2 ) return -1;	// не треугольник
	// если не проверять переполнения
	return ((a%4)==0 || (b%4)==0) && ( (a*a/(c-b)==c+b) || (b*b)/(c-a)==c+a) ;
}
MKuznetsov ★★★★★
()
Ответ на: комментарий от DrBrown

Если для min или max подать равные значения, то там ЕМНИП всегда то что первым подали будет минимумом или максимумом. А подвох я вижу в том, что у тебя меньшее и большее это одно и то же число (переменная) получится если они равны. А то что ошибка через раз прокает, так то запросто от округлений может происходить.

peregrine ★★★★★
()
Ответ на: комментарий от yyk

ок, а почему знак «>»?

с руки было написано :-) конечно знак наоборот. Да и условие лучше a<=c-b .. c гарантировано больше a,b

вот вариант (много кода) для больших чисел, для uint64 максимально детектированная тройка 1615031573306454491,8906783771155260000,18443521773885455084 прямо «под шишечку» UINT64_MAX

template<typename T>
int stdPif(T a,T b,T c)
{
	//
	// со страшной силой избегаем умножений, складываем вычитаем аккуратно
	// чтобы не выходить за разрядную сетку
	//
	if (a>b) std::swap(a,b);
	if (b>c) std::swap(b,c);  // теперь с - потеницальная гипотенуза
	if (a<1 || b<1 || a<=c-b ) return -1;	// a,b,c не треугольник

	if ((a%4)!=0 && (b%4)!=0) return 0;		// один из катетов должен быть кратен 4
	// сокращаем степени 2
	while((a%2)==0 && (b%2)==0 && (c%2)==0) {
		a>>=1;b>>=1;c>>=1;
	}
	// гипотенуза должна быть нечётной
	if ((c%2)==1) {
		// финальная проверка, x-чётный катет(кратный 4) y-нечётный z-гипотенуза
		// проверка x*x==(z-y)(z+y)
		auto fin=[](auto x,auto y,auto z) {
			// aa=x*x чётно, с1=z-y чётное c2=z+y тоже..сразу всё делим на 2
			auto aa=(x)>>1;			 	// a/2
			auto c1=(z>>1)-(y>>1);		//(c-b)/2;
			auto c2=(z>>1)+(y>>1)+1;	//(c+b)/2

			// сокращаем степени 2
			while((aa&1)==0 && (c1&1)==0 && (c2&1)==0 ) {
				aa>>=1;
				c1>>=1;
				c2>>=1;
			}
			while((aa&1)==0 && (c2&3)==0 ) {
				aa>>=1;
				c2>>=2;
			}
			while((aa&1)==0 && (c1&3)==0  ) {
				aa>>=1;
				c1>>=2;
			}
			// перебдеть
			if ((aa>c1 && aa>c2) || (aa<c1 && aa<c2)) return 0; // точно aa*aa!=c1*c2
			// все полученные числа должны быть нечётными
			if ((aa&1)==0 || (c1&1)==0 || (c2&1)==0) {
				return 0;
			}
			// сокращаем оставшийся общий множитель
			auto g=std::gcd(aa,std::gcd(c1,c2));
			aa/=g;c1/=g;c2/=g;
			// хинт: с1,c2 квадраты взаимо-простых нечётных
			if (aa==1 && c1==1 && c2==1) return 1;	// самый частый вариант
			g=std::gcd(aa,c1);
			if (c1/g==g && c1%g==0) {
				aa/=g;
				if (c2/aa==aa && c2%aa==0) return 1;
			}
			return 0;
		};
		///
		if ((a%4)==0 && (b%2)==1) {
			// а кратен 4, b нечётный
			return fin(a,b,c);
		} else
		if ((a%2)==1 && (b%4)==0) {
			// b кратен 4, a нечётный
			return fin(b,a,c);
		}
	}
	return 0;
}
MKuznetsov ★★★★★
()
Ответ на: комментарий от peregrine

В оригинальном примере весь код работает со значениями. В этом случае заморачиваться с тем, на какой объект ссылаются результаты std::min и std::max, просто нет смысла, ведь эти ссылки сразу разыменовываются.

Пара min и max упорядочивает пары чисел. В чём там может быть подвох? Если пара чисел была равной, то какая разница, в каком порядке они вылезут из процедуры сортировки? Я лично не вижу разницы между «2, 2» и «2, 2».

i-rinat ★★★★★
()
Ответ на: комментарий от MKuznetsov

На мой взгляд такой код сложнее, если без привязки к задаче. Так то задача такая, что и в одну строчку можно написать выражение и не заморачиваться.

Мое, исключительно субъективное, мнение:

  1. Надо делать код четко под постановку задачи, не надо расширять ее самостоятельно.

Поэтому не нужно добавлять шаблоны, когда сказано, что входные значения это целые числа от 1 до 3000. (этот шаблон привел к тому, что этот код возможно придется засовывать в заголовочный файл, вместо того, чтобы отдавать в скомпиленной библиотеке).

Поэтому не нужно следить за выходом за разрядность. (а есть 100% уверенность, что при делении на 2 и превращении числа в float не добавится 1 в далеких дробных разрядах?).

  1. Вывод и ввод лучше отделять от основной логики вынося в отдельные функции, т.к. они могут зависеть от кучи факторов. (Я ввод не вынес).

В этом ответе требуемого вывода просто нет. Решение незаконченное.

  1. Чем тупее код, тем он понятней, идеально если он просто следует словестному решению задачи.

В данном случае это проверка на существование треугольника, которая заключается в том, что не должно быть стороны, которая больше суммы других. Ну так это три сравнения, соотв. я их и сделал. Они идут вначале функции, т.е. там, где обычно ставится проверка входных данных, клише.

Проверка того, что треугольник прямоугольный это опять же, проверка того, то квадрат одной стороны равен сумме квадратов двух других. Это опять же всего три варианта. Я их прямо и написал.

Каждая проверка, если успешна, вызывает функцию, которая делает то, что сказано в постановке задачи для этого результата и завершает тест.

Мой код с т.з. дерева принятия решений очень простой. Ну и как мелочь, но полезная мелочь, я даже не меняю значения переменных, что тоже добавляет прозрачности коду.

Про названия функций и переменных… Ну представьте, что в последней строчке есть опечатка, где-нибудь в середине вместо b стоит с, почти рядом на клавиатуре, можно и опечататься. Это же будет незаметно при беглом взгляде. А если в моем коде, вместо square будет написано length, то даже при беглом взгляде будет видна ошибка (так оно и было).

soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 1)
Ответ на: комментарий от peregrine

В округлениях.

В тексте задачи сказано: «На стандартный поток ввода подаётся три целых положительных числа». Но даже если расширить входное пространство до чисел с плавающей точкой, нестабильность сравнений чисел с плавающей точкой проявится только с NaN. Тогда в решении придётся добавить проверку на NaN.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Пара min и max упорядочивает пары чисел. В чём там может быть подвох? Если пара чисел была равной, то какая разница, в каком порядке они вылезут из процедуры сортировки? Я лично не вижу разницы между «2, 2» и «2, 2».

сортировать изначально нах. ненадо - надо просто узнать кто там максимальный. Если уж сортировать то std::sort за глаза - максимум сделает пару лишних тактов (и то не факт, оно скорее соптимизируется).

в рамках задачи любые 2 2 (пары одинаковых значений) это спец-ветка - это точно не пифагорова тройка.

PS/ для особо интересующихся: вы либо используйте уже C++ то есть приводите к std::hypot и 3-м строчкам или С и учебник арифметики (hint: задачник от яндекс, там все вопросы с подковыркой); Задача вообще про криптографию, это их тема - тройки и эллипсы..

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

надо просто узнать кто там максимальный.

ТС именно это и делает. По сути - одна итерация «пузырька».

Код который Вы привели как образец - это «ужас ужас». В приличных домах за такое увольняют.

bugfixer ★★★★★
()