1. 程式人生 > >矩陣翻硬幣(C++)

矩陣翻硬幣(C++)

問題描述
  小明先把硬幣擺成了一個 n 行 m 列的矩陣。

  隨後,小明對每一個硬幣分別進行一次 Q 操作。

  對第x行第y列的硬幣進行 Q 操作的定義:將所有第 i*x 行,第 j*y 列的硬幣進行翻轉。

  其中i和j為任意使操作可行的正整數,行號和列號都是從1開始。

  當小明對所有硬幣都進行了一次 Q 操作後,他發現了一個奇蹟——所有硬幣均為正面朝上。

  小明想知道最開始有多少枚硬幣是反面朝上的。於是,他向他的好朋友小M尋求幫助。

  聰明的小M告訴小明,只需要對所有硬幣再進行一次Q操作,即可恢復到最開始的狀態。然而小明很懶,不願意照做。於是小明希望你給出他更好的方法。幫他計算出答案。
輸入格式
  輸入資料包含一行,兩個正整數 n m,含義見題目描述。
輸出格式
  輸出一個正整數,表示最開始有多少枚硬幣是反面朝上的。
樣例輸入
2 3
樣例輸出
1
資料規模和約定
  對於10%的資料,n、m <= 10^3;
  對於20%的資料,n、m <= 10^7;
  對於40%的資料,n、m <= 10^15;
  對於10%的資料,n、m <= 10^1000(10的1000次方)。
本來以為還是一道模擬題,聰(bai)明(chi)的小M也是too young too simple,其實,看到題目的資料範圍我們應該已經感覺到,模擬絕對沒戲,接下來我們仔細分析一下這道題的解法。

1.很容易得出,如果一枚硬幣被翻了奇數次,那麼它原來的狀態肯定是反面朝上,所以,我們要找的就是被翻了奇數次的硬幣

  1. Q 操作的定義:將所有第 i*x 行,第 j*y 列的硬幣進行翻轉。正向看可能不好想,那麼我們反向看一下,對於一個橫座標為N的硬幣,在翻哪些硬幣(橫座標x)的時候會翻到它呢?其實就是這個數N所有的約數,比如橫座標為4的硬幣,那麼,在翻橫座標為1,2,4的硬幣時都會翻到它,縱座標的情況是一樣的。

3.對於一個硬幣,我們必須同時考慮其橫座標x和縱座標y,假如橫座標被翻了a次,縱座標被翻了b次,則這個硬幣總共被翻了a*b次,若想要這個硬幣被翻奇數次,a和b必須都得是奇數,即x和y都有奇數個約數

4.那麼問題來了:哪些數有奇數個約數呢?不管你知不知道,反正現在你知道了,完全平方數有奇數個約數。那麼什麼又是完全平方數呢,簡單的說就是n^2,n為自然數,也就是0,1,4,9……

5.問題又來了,怎麼求完全平方數的個數呢,首先,我們已經知道了這個矩陣式n*m的,而且是從1開始編號的,對於n,我們可以求sqrt(n),然後取整,容易想出,在1-n的範圍內的完全平方數的個數為(int)(sqrt(n))個,而sqrt(n)*sqrt(m)就是所有的橫縱座標都是完全平方數的硬幣的個數。

6.下面,我們迎來了終極問題,題目思路有了,但是超大規模的資料問題並沒有得到解決,反而更麻煩了,因為居然還搞出了個開方的操作,但很不幸,這就是一道悲催的大數高精度題,而且還得自己寫出大數相乘,大數開方,大數比較等操作

OK,下面開始就全部都是大數運算的相關知識了,如果你已經很熟悉可以無視

首先,讓我們明確一下思路,到底如何實現乘法和開方,對於大數的儲存,我們使用string,因為它的長度比較容易控制

乘法:其實有很多速度快而且更巧妙的大數乘法,但是在藍橋杯,我們只要使用模擬手演算法應該是問過,雖說是模擬,但也有一些我們常用但不太瞭解的規律,我們在手算乘法的時候,需要進行移位和進位,這兩個操作我們會通過兩步完成

1.移位:對於兩個數a=12,b=25,在相乘的時候我們讓每一位數分別相乘,即a[0]*b[0]=2 , a[0]*b[1]=5 , a[1]*b[0]=4 , a[1]*b[1]=10,那麼規律就是,對於兩個數a[i] , b[j],所有i+j相同的數都要加到一起,所以我們要把5+4=9合併為一個數,也就是說,將每一位數相乘之後,我們實際得到了2,9,10三個沒有進位的數

2.進位:通過上面的操作,我們的2,9,10三個沒有進位的數,下面就要進行進位,因為我們是把高位相乘得到的數放在前面,地位相乘得到的數放在後面,所以這三個數排列自然也是高位在前,地位在後,所以要從右向左進位,進位的方法就是a[i]=a[i+1]/10 , a[i+1]=a[i+1]%10,如果放到這三個數上面,就是,9+10/10=10,然後10%10=0,這三個數變成2,10,0,再一次就是2+10/10=3,10%10=0,三個數變為3,0,0,而這正是我們要求的結果,在實際操作中,我們習慣於將數倒著存放,即將數存為10,9,2,這是為了進位方便,因為如果正序的話如果最高位發生進位我們就要將每一個數向後移動一位從而挪出一個空位,想想都麻煩,倒序的話因為是向後進位,想怎麼進就怎麼進

開方:這個開方方法不是我想出來的,是參照了網上的一個方法,我感覺挺好。實際上也可以用牛頓逼近法。

這個方法的前提:假如一個數有偶數位n,那麼這個數的方根有n/2位;如果n為奇數,那麼方根為(n+1)/2位。

然後,讓我們實際看一個例子,我們假設這個數就是1200

1.很明顯,它有4位,所以它的方根有2位,然後,我們通過下面的方法來枚舉出它的整數根

00*00=0<1200

10*10=100<1200

20*20=400<1200

30*30=900<1200

  40*40=1600>1200

所以,這個根的十位就是3,然後,再列舉個位

                                                31*31=961<1200

32*32=1024<1200

33*33=1089<1200

34*34=1156<1200

35*35=1225>1200

所以,這個根就是34,因為平方增長的速度還是比較快的,所以速度沒有太大問題,這裡有一個地方需要處理一下,如果一個數很長,那麼它的方根的位數也比較長,在列舉高位的時候,我們沒有必要把後面的0都加上,因為那樣會影響速度,其實0的個數完全可以算出來,然後將0的個數一起送入比較函式比較即可,這樣可以提高速度

比較:上面我們說過,比較函式接受的兩個數只有一個是完整的數,另外一個實際上是高几位的平方和0的個數,但處理方式差不多,都是先比較位數,位數大數就大,位數一樣就逐位比較,只要別忘了比較那一堆0就好了,最後其實不用把情況分的太清楚,只要返回0,1就行,因為只有當一個數大於n的時候才對我們有意義

#include<iostream>
#include<string>
#include<cstring>
using namespace std;


string strMul(string a,string b)
{
    string result="";
    int len1=a.length();
    int len2=b.length();
    int i,j;
    int num[500]={0};
    for(i=0;i<len1;i++)
    for(j=0;j<len2;j++)
    {
        num[len1-1+len2-1-i-j]=num[len1-1+len2-1-i-j]+(a[i]-'0')*(b[j]-'0');
    }

    //for(i=0;i<5;i++)
    //cout<<num[i]<<' ';
    //cout<<endl;

    for(i=0;i<len1+len2;i++)
    {
        num[i+1]=num[i+1]+num[i]/10;
        num[i]=num[i]%10;
    }

    //for(i=0;i<5;i++)
    //cout<<num[i]<<' ';

    for(i=len1+len2-1;i>=0;i--)
    {
        if(num[i]!=0)
        break;
    }
    for(;i>=0;i--)
    {
        result=result+(char)(num[i]+'0');
    }
    return result;
}

int strCmp(string a,string b,int pos)
{
    int i;
    //cout<<a<<endl;
    //cout<<a.length()<<' '<<b.length()<<endl;
    if(a.length()+pos>b.length())
    return 1;
    if(a.length()+pos<b.length())
    return 0;
    if(a.length()+pos==b.length())
    {
        for(i=0;i<a.length();i++)
        {
            if(a[i]<b[i])
            return 0;
            if(a[i]==b[i])
            continue;
            if(a[i]>b[i])
            return 1;
        }

    }
}

string strSqrt(string a)
{
    string result="";
    int i;
    int len=a.length();
    if(len%2==0)
    len=len/2;
    else
    len=len/2+1;
    for(i=0;i<len;i++)
    {
        result=result+'0';
        while(strCmp(strMul(result,result),a,2*(len-1-i))!=1)
        {
            if(result[i]==':')
            break;
            result[i]++;
        }
        result[i]--;
    }
    return result;
}

int main()
{
    string n,m;
    cin>>n>>m;
    cout<<strMul(strSqrt(n),strSqrt(m))<<endl;
    //cout<<strMul("35","35")<<endl;
    //cout<<strCmp("1024","1200",0);
    //cout<<strSqrt("9801");
    return 0;
}

相關推薦

矩陣硬幣C++

問題描述   小明先把硬幣擺成了一個 n 行 m 列的矩陣。   隨後,小明對每一個硬幣分別進行一次 Q 操作。   對第x行第y列的硬幣進行 Q 操作的定義:將所有第 i*x 行,第 j*y 列的硬幣進行翻轉。   其中i和j為任意使操作可行的正整數,

藍橋杯 矩陣硬幣打表+二分

網上其他人的答案好像都是用數學方法解決的。做這道題的時候一看就感覺是找規律的題,所以先打個表。因為後面要用到大數處理,所以是Java語言打表程式碼(就是按題目意思暴力):import java.io.FileNotFoundException; import java.io.

藍橋杯:矩陣硬幣大數開根號

  對於10%的資料,n、m <= 10^3;  對於20%的資料,n、m <= 10^7;  對於40%的資料,n、m <= 10^15;  對於10%的資料,n、m <= 10^1000(10的1000次方)。 我的思路:他是問翻之前有多少個硬幣是反面朝上的,所以這個反面朝上的

藍橋杯 歷屆試題 硬幣貪心

小明正在玩一個“翻硬幣”的遊戲。 桌上放著排成一排的若干硬幣。我們用 * 表示正面,用 o 表示反面(是小寫字母,不是零)。 比如,可能情形是:**oo***oooo 如果同時翻轉左邊的兩個硬幣,則變為:oooo***oooo 現在小明的問題是:如果已知了初始狀態和要達到的目標狀態,每次只能同時翻

藍橋杯 歷屆試題 硬幣Java

小明正在玩一個“翻硬幣”的遊戲。 桌上放著排成一排的若干硬幣。我們用 * 表示正面,用 o 表示反面(是小寫字母,不是零)。 比如,可能情形是:**oo***oooo 如果同時翻轉左邊的兩個硬幣,則變為:oooo***oooo 現在小明的問題是:如果已知了初始狀態和要達到的目標狀態,每次只能同時翻

圖相關圖的鄰接矩陣表示C++及圖的遍歷

一.圖的鄰接矩陣表示法 struct graph { vector<vector<int>> cost;//鄰接矩陣 vector<string> vertex;//頂點的值,用string較好,節點的名字可以是v1,v

LeetCode 54. 螺旋矩陣 Spiral MatrixC語言

題目描述: 給定一個包含 m x n 個元素的矩陣(m 行, n 列),請按照順時針螺旋順序,返回矩陣中的所有元素。 示例 1: 輸入: [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ] 輸出: [1,2,3,6,9,8,7,4,5]

圖相關圖的鄰接矩陣表示C++-拓撲排序

一.測試用圖 二.拓撲排序 void toposort(graph g) {//注意,拓撲排序中要把鄰接矩陣中沒有邊的位置置為0(為了統計入度) size_t N = g.cost.size(); vector<int> InDegree(N, 0);//統

圖相關圖的鄰接矩陣表示C++及最最小生成樹演算法prim和kruskal

一.測試用圖 鄰接矩陣表示: //prim注意是無向圖 vector<vector<int>> p(6, vector<int>(6, INF));//類似dijikstra,沒有邊的點設為INF p[0][1] = 10;

圖相關圖的鄰接矩陣表示C++及最短路徑演算法

一.Dijikstra演算法 注意:計算最短路徑時,需要把鄰接矩陣中沒有邊的位置初始化為無窮大;此處以INF表示,INF可以取0x3f3f3f3f,不能直接設為INT_MAX(因為做加法時會上溢)。 測試用圖: 其鄰接矩陣表示為: vector<vector<int

演算法-藍橋杯-演算法提高 矩陣相乘 C++

1 引言    矩陣乘法,以前做過。2 題目問題描述  小明最近在為線性代數而頭疼,線性代數確實很抽象(也很無聊),可惜他的老師正在講這矩陣乘法這一段內容。  當然,小明上課打瞌睡也沒問題,但線性代數的習題可是很可怕的。  小明希望你來幫他完成這個任務。  現在給你一個ai行

硬幣 藍橋杯

上題 時間限制: 1 Sec 記憶體限制: 128 MB 提交: 29 解決: 18 [提交][狀態][討論版] 題目描述 小明正在玩一個“翻硬幣”的遊戲。 桌上放著排成一排的若干硬幣。我們用 * 表示正面,用 o 表

歷屆試題 硬幣 藍橋杯

小明正在玩一個“翻硬幣”的遊戲。桌上放著排成一排的若干硬幣。我們用 * 表示正面,用 o 表示反面(是小寫字母,不是零)。比如,可能情形是:**oo***oooo如果同時翻轉左邊的兩個硬幣,則變為:oooo***oooo現在小明的問題是:如果已知了初始狀態和要達到的目標狀態,每次只能同時翻轉相鄰的兩個硬幣,那

C++實戰之OpenCL矩陣相乘優化

前言 上一篇文章,分析了簡單的矩陣相乘在opencl裡面的優化kernel程式碼,每個work-item只負責計算結果矩陣的一個元素。下一步準備每次計算出結果矩陣的塊元素,看看計算時間是如何。 具體分析 這裡引入opencl記憶體的概念: 比較常

高斯約當法求逆矩陣的演算法實現C++

#include"iostream.h" #include"math.h" void main() { float a[10][10],A[10][10],b[10],c[10][10],d=0,f=0; int i=0,j=0,k=1,l=0,m=0,n=0; //---

c++資料結構與演算法之圖:鄰接矩陣、深度廣度遍歷、構造最小生成樹prim、kruskal演算法

//圖的鄰接矩陣實現 //廣度遍歷bfs和深度遍歷dfs //構造最小生成樹的prim、kruskal演算法 #include <iostream> #include<stack> #include<queue> #define WEI

C#日誌接口請求響應時間

ide test isnull pty directory pps 請求方式 rri == 日誌接口響應時間,記錄接口請求信息,響應結果以及響應時間等。可以清楚的分析和了解接口現在。 如果一個一個地在接口下面做日誌,那不是我們想要的結果。所以,我們選擇做一個特性來控制接口要

LeetCode | Reverse Words in a StringC#

++ style str blog cnblogs count item leetcode string 題目: Given an input string, reverse the string word by word. For example,Given s = "

算法 - 求一個數組的最長遞減子序列C++

str log bst article subst else from return ear //************************************************************************************

2~62位任意進制轉換c++

rtmp end iostream 思路 [0 代碼 () objc blog 進制轉換的符號表為[0-9a-zA-Z],共61個字符,最大可表示62進制。 思路是原進制先轉換為10進制,再轉換到目標進制。 疑問:   對於負數,有小夥伴說可以直接將符號丟棄,按照整數