iT邦幫忙

0

(更)C++題目 x^x+y^y+z^z==h^h 邏輯錯誤

c++
  • 分享至 

  • xImage

題目如下:
https://ithelp.ithome.com.tw/upload/images/20221015/20153865tHz6pjQuNm.png

昨天詢問老師,老師有給提示,是要用x^2+y^2+z^2==h^2的概念作題。我打到最後卡在這:

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

int main(void){
    int a,b,h,i,j,k,N=0;
    char sign;
    cin >> a >> b;
    for(h=a;h<=b;h++){
        if(h%2==1){
            sign='-';
        }
        else{
            sign='+';
        }
        for(i=0;i<h+1;i++){
            for(j=0;j<h+1;j++){
                for(k=0;k<h+1;k++){
                    if(((i^2)+(j^2)+(k^2))==(h^2)){
                        N=N+2;}}}}
        cout << h << "\t" << sign << "\t" << N << "\n";
        N=0;
    }
    return 0;
}

我是把X,Y,Z軸值用i,j,k的for迴圈呈現,但好像有問題。請問有大大能幫我看看是哪裡邏輯錯誤嗎?

---更---

感謝大大解惑,平方那邊的問題解決了。我現在把ijk那邊迴圈的初始值改成-h,最裡面if迴圈的N+2改成N+1,如下:

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

int main(void){
    int a,b,h,i,j,k,N=0;
    char sign;
    cin >> a >> b;
    for(h=a;h<=b;h++){
        if(h%2==1){
            sign='-';
        }
        else{
            sign='+';
        }
        for(i=0-h;i<h+1;i++){
            for(j=0-h;j<h+1;j++){
                for(k=0-h;k<h+1;k++){
                    if(((i*i)+(j*j)+(k*k))==(h*h)){
                        N=N+1;
                    }
                }
            }
        }
        cout << h << "\t" << sign << "\t" << N << "\n";
        N=0;
    }
    return 0;
}

但還是有bug,請問還有哪裡有問題?

看更多先前的討論...收起先前的討論...
淺水員 iT邦大師 6 級 ‧ 2022-10-16 00:01:16 檢舉
C語言的 ^ 是 XOR,不是指數
可以寫 i*i+j*j+k*k==h*h

另外分母不一定會是連續整數
例如根號7是不存在的
所以 h%2 不能用來判斷正負號
iT邦新手 4 級 ‧ 2022-10-16 00:11:46 檢舉
你是怎麼看出 N 需要在判斷式等號成立時 +2 的?
@貴 因為xyz軸各有正負 我的i j k只有正數 而一座標(i,j,k)符合的時候 以(0,0,0) 為對稱點也會有另一個座標(-i,-j,-k)符合 所以+2是+這兩個座標 還是這其實有問題?
iT邦新手 4 級 ‧ 2022-10-16 01:27:40 檢舉
你的 h 從程式碼看起來是用來代表無窮數列的第 m 到 n 項,看不出跟 xyz 有啥關係

需要計算的主要是三角形斜邊跟長方體或正方體的對角線,不知道你的老師說的判斷式要用在哪邊;不過如果把這想成一個空間中的座標,每個原子都剛好位於 x,y,z 軸的整數上,每個原子與原點的距離 D 確實可用 x,y,z 座標去算出來

空間中的這些原子分成三類,第一類是位於三個軸上的原子,也就是上下左右前後共會有六個原子,每次都以距離原點 r 的距離一直延伸出去,永遠都只有六個;第二類是位於 xy、yz、xz 平面上的原子,且又分成距離原點的為正方形的對角線或長方形的對角線;第三類是前兩類以外的原子,既不在三軸上,也不在三軸形成的平面上,因此與原點的距離就變成正方體或長方體的對角線

靠窮舉把所有空間座標中的整數座標距離原點的距離都算出來再把所有同距離的點累加,是你的程式碼想達成的

但無窮數列的第幾項跟距離沒有直接關係,你的 i,j,k 也不是整數而是正整數,所以 N 得不到正確的值

看起來需要一個從 1 開始的距離變數,在三層迴圈跑完後檢查 N 是否等於 0,若為 0 就把距離變數加 1 ,若不為 0 ,則把 N 放入 h 陣列,h 陣列的索引 i 代表無窮數列的第 i+1 項

i,j,k 的平方和是否等於距離變數的平方,若為真繼續判斷 i,j,k的值,若 i,j,k 包含兩個 0,也就是在軸上,代表 N 要等於 N + 2;若 i,j,k 包含一個 0,也就是在 xy,yz,xz 平面上,代表 N 要等於 N + 4;若 i,j,k 都不含 0,也就是在八個象限中的其中一個象限,代表 N 要等於 N+8
@貴 感謝解答 我去用if elseif來判斷+2 +4 +8看看行不行得通
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 個回答

0
淺水員
iT邦大師 6 級 ‧ 2022-10-17 02:01:47

提供參考
我的作法是慢慢加大盒子大小
每次增加盒子大小就把「座標點」寫到陣列(可排序後輸出)

座標點的表達我是規定從大排到小
例如:(5,5,2) 這組數字,代表了 https://chart.googleapis.com/chart?cht=tx&amp;chl=2%5E3%20%5Ctimes%20C%5E3_1 個座標點

#include <iostream>
#include <vector>
#include <algorithm>

class Point
{
private:
    unsigned x, y, z; //座標(限定 x >= y >= z >= 0)
    unsigned step;    //步長(奇偶數決定正負號)
    unsigned r2;      //半徑平方
    unsigned n;       //對稱點數目
    bool xAxis;       //是否在 x 軸上

public:
    Point(unsigned, unsigned, unsigned); //建構,參數請從大排到小
    bool isXAxis();                      //這個點是否在 x 軸上
    unsigned _getNum();                  //取得對稱點數目
    bool operator<(const Point &);       //用於 std::sort
    friend std::ostream &operator<<(std::ostream &, const Point &); //輸出
};

class BoxPoints
{
private:
    unsigned r;   //盒子中心到表面的最短距離
    unsigned idx; //讀取到的位置
    std::vector<Point> points; //收集到的點
    void _extendRadius(); //加大盒子大小,更新 r、points、idx

public:
    BoxPoints();
    Point getNext(); //取得下一個點
};

int main()
{
    BoxPoints box;
    for (int i = 0; i < 20; ++i)
    {
        std::cout << box.getNext();
    }
    return 0;
}

Point::Point(unsigned px, unsigned py, unsigned pz)
{
    x = px;
    y = py;
    z = pz;
    step = px + py + pz;
    r2 = px * px + py * py + pz * pz;
    n = _getNum();
    xAxis = (y == 0 && z == 0);
}

bool Point::isXAxis()
{
    return xAxis;
}

unsigned Point::_getNum()
{
    unsigned t = 1;
    if (x > 0)
    {
        t <<= 1;
    }
    if (y > 0)
    {
        t <<= 1;
    }
    if (z > 0)
    {
        t <<= 1;
    }
    if (x == y)
    {
        if (y == z)
        {
            return t;
        }
        else
        {
            return t * 3;
        }
    }
    else
    {
        if (y == z)
        {
            return t * 3;
        }
        else
        {
            return t * 6;
        }
    }
}

bool Point::operator<(const Point &p)
{
    return r2 != p.r2 ? (r2 < p.r2) : (step < p.step);
}

std::ostream &operator<<(std::ostream &os, const Point &p)
{
    os << "(" << p.x << "," << p.y << "," << p.z << ") => "
       << (p.step % 2 ? "-" : "+") << p.n << "/" << p.r2 << " (" << p.step << ")\n";
    return os;
}

BoxPoints::BoxPoints()
{
    r = 0;
    idx = 0; //讀取的索引
    _extendRadius();
}

Point BoxPoints::getNext()
{
    Point p = points[idx++];
    if (p.isXAxis())
    {
        points.erase(points.begin(), points.begin() + idx);
        idx = 0;
        _extendRadius();
    }
    return p;
}

void BoxPoints::_extendRadius()
{
    unsigned x = ++r;
    for (int y = x; y >= 0; --y)
    {
        for (int z = y; z >= 0; --z)
        {
            points.push_back(Point(x, y, z));
        }
    }
    std::sort(points.begin(), points.end());
}

/*
輸出結果
(1,0,0) => -6/1 (1)
(1,1,0) => +12/2 (2)
(1,1,1) => -8/3 (3)
(2,0,0) => +6/4 (2)
(2,1,0) => -24/5 (3)
(2,1,1) => +24/6 (4)
(2,2,0) => +12/8 (4)
(3,0,0) => -6/9 (3)
(2,2,1) => -24/9 (5)
(3,1,0) => +24/10 (4)
(3,1,1) => -24/11 (5)
(2,2,2) => +8/12 (6)
(3,2,0) => -24/13 (5)
(3,2,1) => +48/14 (6)
(4,0,0) => +6/16 (4)
(4,1,0) => -24/17 (5)
(3,2,2) => -24/17 (7)
(3,3,0) => +12/18 (6)
(4,1,1) => +24/18 (6)
(3,3,1) => -24/19 (7)
*/
看更多先前的回應...收起先前的回應...

感謝大大回應!我已經用我上面的程式結構寫完了,但有runtime error的問題(輸入的數太大,導致運算時間太久)。請問有簡化我的三層巢氏的方法嗎?

淺水員 iT邦大師 6 級 ‧ 2022-10-18 15:25:50 檢舉

要加快速度,比較簡單的方式是:直接跑最大的區域,記錄下「半徑平方」與「個數」之間的關係。之後輸出時查詢紀錄就可。這樣可以避免內部方塊重複計算的時間。

#include <iostream>
#include <map>

int main(void)
{
    int a, b, h, i, j, k, r, N;
    char sign;
    std::map<int, int> record;

    //輸入範圍
    std::cin >> a >> b;

    //直接跑最大的區域 (-b~b, -b~b, -b~b),並記錄個數
    for (i = -b; i <= b; i++) {
        for (j = -b; j <= b; j++) {
            for (k = -b; k <= b; k++) {
                r = i * i + j * j + k * k;
                if (record.find(r) == record.end()) {
                    record[r] = 1; //如果紀錄中沒有 r 設為 1
                } else {
                    record[r] += 1; //若已有紀錄,增加 1
                }
            }
        }
    }

    //輸出時直接從記錄中去找
    for (h = a; h <= b; ++h) {
        if (record.find(h) != record.end()) {
            N = record[h];
            sign = h % 2 ? '-' : '+';
            std::cout << h << "\t" << sign << "\t" << N << "\n";
        }
    }

    return 0;
}
淺水員 iT邦大師 6 級 ‧ 2022-10-18 15:37:17 檢舉

接續上面,進一步利用對稱關係,可以再加快速度
(迴圈數大概是原本的 1/32 左右)

#include <iostream>
#include <map>

int getNum(int x, int y, int z)
{
    int t = 1;
    if (x > 0) {
        t <<= 1;
    }
    if (y > 0) {
        t <<= 1;
    }
    if (z > 0) {
        t <<= 1;
    }
    if (x == y) {
        if (y == z) {
            return t;
        } else {
            return t * 3;
        }
    } else {
        if (y == z) {
            return t * 3;
        } else {
            return t * 6;
        }
    }
}

int main(void)
{
    int a, b, h, i, j, k, r, N;
    char sign;
    std::map<int, int> record;

    //輸入範圍
    std::cin >> a >> b;

    //直接跑最大的區域,並記錄個數
    for (i = 1; i <= b; i++) {
        for (j = 0; j <= i; j++) {
            for (k = 0; k <= j; k++) {
                //注意這邊 i >= j >= k,用 getNum 算對稱點個數
                r = i * i + j * j + k * k;
                if (record.find(r) == record.end()) {
                    record[r] = getNum(i, j, k); //如果紀錄中沒有 r 設為個數
                } else {
                    record[r] += getNum(i, j, k); //若已有紀錄,增加個數
                }
            }
        }
    }

    //輸出時直接從記錄中去找
    for (h = a; h <= b; ++h) {
        if (record.find(h) != record.end()) {
            N = record[h];
            sign = h % 2 ? '-' : '+';
            std::cout << h << "\t" << sign << "\t" << N << "\n";
        }
    }

    return 0;
}

sorry大大,我還看不太懂你打的,但能請你幫我從我修改好的來優化嗎?

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

int main(void){
    int a,b,h,i,j,k,N=0;
    char sign;
    cin >> a >> b;
    for(h=a;h<=b;h++){
        if(h%2==1){
            sign='-';
        }
        else{
            sign='+';
        }
        for(i=0-h;i<h+1;i++){
            for(j=0-h;j<h+1;j++){
                for(k=0-h;k<h+1;k++){
                    if(((i*i)+(j*j)+(k*k))==h){
                        N=N+1;
                    }
                }
            }
        }
        cout << h << "\t" << sign << "\t" << N << "\n";
        N=0;
    }
    return 0;
}
淺水員 iT邦大師 6 級 ‧ 2022-10-21 00:57:25 檢舉

先前我提到

要加快速度,比較簡單的方式是:直接跑最大的區域,記錄下「半徑平方」與「個數」之間的關係。之後輸出時查詢紀錄就可。這樣可以避免內部方塊重複計算的時間。

就是從你寫的東西改的,已經盡量不改太多了

你看不懂可能是沒學過內建的容器(例如 Map)
下面先把 Map 改成一般陣列,先看懂我先前表達的意思
之後還是建議學一下 Map 的用法

#include <iostream>

int main(void)
{
    int a, b, h, i, j, k, r, N;
    char sign;
    int* record;

    //輸入範圍
    std::cin >> a >> b;
    
    //改用陣列紀錄,缺點是比起 Map 很浪費空間
    //b 的 3 次方很浪費記憶體,測試時不要輸入太大
    record = new int[b * b * b](); 

    //直接跑最大的區域 (-b~b, -b~b, -b~b),並記錄個數
    for (i = -b; i <= b; i++) {
        for (j = -b; j <= b; j++) {
            for (k = -b; k <= b; k++) {
                r = i * i + j * j + k * k;
                record[r] += 1;
            }
        }
    }

    //輸出時直接從記錄中去找
    for (h = a; h <= b; ++h) {
        if (record[h] > 0) {
            N = record[h];
            sign = h % 2 ? '-' : '+';
            std::cout << h << "\t" << sign << "\t" << N << "\n";
        }
    }

    delete[] record;
    return 0;
}

感謝!但後來老師有給提示是說不用掃整個立方體,所以三層巢氏應該是沒錯的,只是迴圈的範圍問題而已。
ps. 老師有故意input 9000 9010,所以全部掃完應該是沒辦法qwq。

0
JamesDoge
iT邦高手 1 級 ‧ 2023-02-13 08:27:37

修改後

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

int main(void){
    int a,b,h,i,j,k,N=0;
    char sign;
    cin >> a >> b;
    for(h=a;h<=b;h++){
        if(h%2==1){
            sign='-';
        }
        else{
            sign='+';
        }
        for(i=-h;i<=h;i++){
            for(j=-h;j<=h;j++){
                for(k=-h;k<=h;k++){
                    if(((i*i)+(j*j)+(k*k))==(h*h)){
                        N=N+1;
                    }
                }
            }
        }
        cout << h << "\t" << sign << "\t" << N << "\n";
        N=0;
    }
    return 0;
}

我要發表回答

立即登入回答