AcWing 1221. 四平方和 三种解法 java 二分 哈希 暴力

👨‍🏫 参考链接
🙈 题目
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多 4个正整数的平方和。
如果把 0包括进去,就正好可以表示为 4个数的平方和。
比如:
5=02+02+12+22
7=12+12+12+22
对于一个给定的正整数,可能存在多种平方和的表示法。要求你对 4
个数排序:
0≤a≤b≤c≤d
并对所有的可能表示法按 a,b,c,d
为联合主键升序排列,最后输出第一个表示法。

🙈 输入格式
输入一个正整数 N

🙈 输出格式
输出4个非负整数,按从小到大排序,中间用空格分开。

🙈 数据范围
0<N<5∗106

🙈 输入1

5

🙈 输出1

0 0 1 2

🤠 数据范围 + 时间限制 = 可以枚举多少个数

🤠 结果 a b c d ,枚举的时候可人为规定 a < b < c < d
🤠 只能枚举两个数 c,d 存下所有 c^2 + d^2 的 和
🤠 后边再枚举 a,b 算下 n - ( a^2 + b^2)是否有在前边的 a b 平方和出现过,出现过即是一个解

🤠 如何求字典序最小呢?
c d 是从小到大枚举的,而且 c <= d ,
a b 也是从小到大枚举的,第一个解就是最小字典序,然后 n - (a^2 + b^2 ) = t ,在 c d 中找到第一个 平方和等于 t 的就是最小字典序,所以说,枚举 c d 的时候相同的 平方和 t 只用存储最小字典序的那对也就是 第一次出现的

🧐 有意思的是,此题暴力枚举三个数的效率反而是最高的
🧐 理论时间复杂度不一定准确,一切以实际为准,受教了

👵 暴力解法 O( n^3 )

import java.io.*;
import java.util.*;

public class 四平方和
{
	public static void main(String[] args)
	{
		int n = new Scanner(System.in).nextInt();

		for (int a = 0; a * a <= n; a++)
			for (int b = a; a * a + b * b <= n; b++)
				for (int c = b; a * a + b * b + c * c <= n; c++)
				{
					int t = n - (a * a + b * b + c * c);
					int d = (int) Math.sqrt(t);
					if (d * d == t)
					{
						System.out.println(a + " " + b + " " + c + " " + d);
						System.exit(0);
					}
				}
	}
}

👵 模拟哈希 O( n^2 )

import java.util.Arrays;
import java.util.Scanner;

// 模拟哈希
public class 四平方和2
{
	static int N = 5000010;
	static int[] C = new int[N];// 存 c,顺便用 -1 代表 null,间接实现去重,记录 平方和是 index 的最新字典序 c d
	static int[] D = new int[N];// 存 d

	public static void main(String[] args)
	{
		int n = new Scanner(System.in).nextInt();

		Arrays.fill(C, 0, n + 1, -1);
		for (int c = 0; c * c <= n; c++)
			for (int d = c; c * c + d * d <= n; d++)
			{
				int t = c * c + d * d;
				if (C[t] == -1)
				{
					C[t] = c;
					D[t] = d;
				}
			}

		for (int a = 0; a * a <= n; a++)
			for (int b = a; a * a + b * b <= n; b++)
			{
				int t = n - (a * a + b * b);
				if (C[t] != -1)
				{
					System.out.println(a + " " + b + " " + C[t] + " " + D[t]);
					System.exit(0);
				}
			}
	}
}

👵 二分 O( n^2 logn) (此题不建议,得自定义字典序排序函数)
👵 大佬代码

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 5000010;
int n, m;

struct Sum//维护字典序
{
    int s, c, d;
    bool operator< (const Sum &t) const//重载小于号
    {
        if (s != t.s) return s < t.s;//先按总和从小到大排序
        if (c != t.c) return c < t.c;//若总和相同,则按照c从小到大排序
        return d < t.d;              //若总和与c均相同,则按照d从小到大排序
    }
}S[N];

int main()
{
    scanf("%d", &n);

    for (int c = 0; c * c <= n; c ++)
        for (int d = c; c * c + d * d <= n; d ++)
            S[m ++] = {c * c + d * d, c, d};

    sort(S, S + m);

    for (int a = 0; a * a <= n; a ++)
        for (int b = a; a * a + b * b <= n; b ++)
        {
            int t = n - a * a - b * b;

            int l = 0, r = m - 1;
            while (l < r)
            {
                int mid = l + r >> 1;
                if (S[mid].s >= t) r = mid;
                else l = mid + 1;
            }

            if (S[l].s == t)
            {
                printf("%d %d %d %d\n", a, b, S[l].c, S[l].d);
                return 0;
            }
        }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值