1. 程式人生 > 實用技巧 >洛谷P7071—優秀的拆分(2020CSP-J第一題)

洛谷P7071—優秀的拆分(2020CSP-J第一題)

去考試的時候硬是將這道題打了個\(dfs\),只過了\(80\)%的資料,回家路上才突然醒悟這道題的正解思路。

原來此題這麼簡單

“看來是智商日常不線上吧”

                                                     —— gyh

題目傳送門

題面簡述

給定正整數\(n\),你需要判斷這個數的所有拆分中,是否存在優秀的
拆分。若存在,請你給出具體的拆分方案(從大到小輸出,空格隔開)。

優秀的拆分定義:

對於正整數\(n\)的一種特定拆分,我們稱它為“優秀的”,當且僅當在這種拆分下,\(n\)被分解為了若干個不同的2的正整數次冪。

正解思路

“任何一個正整數都可以拆分成若干個\(2\)
的非負整數次冪之和。”

                                                     ——《小學奧數》

既然是\(2\)的非負整數次冪之和,我們可以直接將它轉換為\(2\)進位制

例如:
\(n=24_{10}=11000_{2}\)

然後從大到小輸出這一位是\(1\)的位權值

例如:
\(n=24_{10}=11000_{2}=16_{10}(10000_{2})+8_{10}(1000_{2})\)

輸出結果就是

\(16\) \(8\)

另外,所有的奇數都木有優秀的拆分

因為奇數包含\(2^0\)

0不是正整數(不會吧不會吧,不會真的有人不知道0不是正整數吧)

如果是奇數,直接輸出\(-1\)

就行了。

上程式碼———

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    freopen("power.in","r",stdin);
    freopen("power.out","w",stdout);//競賽千萬不要忘了這個
    int n,i=0;
    bool w[50];//由於只有0與1,可以使用布林陣列儲存2進位制數
    cin>>n;
    if(n&1)//等同於n%2==1,但是按位與運算速度更快
    	cout<<-1;
    else
    {
    	while(n)
    	    w[i++]=n&1,n>>=1;//等同於w[i++]=n%2,n/=2;
    	for(i=i-1;i>=0;i--)
    	    if(w[i])
                cout<<(1<<i)<<" ";//輸出2^i
    }
    return 0;//不要忘了寫
}

其實還可以加一些優化,由於\(2^i\)的值是固定的,可以給\(2\)的非負整數次冪打一張表,輸出時可以直接呼叫

優化後的程式碼———

#include<iostream>
#include<cstdio>
using namespace std;
const int hh[50]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216};//最大到10^7,int可以存下
int main()
{
    freopen("power.in","r",stdin);
    freopen("power.out","w",stdout);
    int n,i=0;
    bool w[50];
    cin>>n;
    if(n&1)
    cout<<-1;
    else
    {
        while(n)
           w[i++]=n&1,n>>=1;
        for(i=i-1;i>=0;i--)
           if(w[i])
	       cout<<hh[i]<<" ";//直接輸出2^i
    }
    return 0;
}

這樣速度就很快啦~~

時間複雜度是\(O(2logn)\)

其實還可以再優化啦~~

再優化就要靠對位運算的熟練運用

從大到小判斷\(n\)的這一位是不是\(1\),是\(1\)就輸出這一位的權值。

思路和上面差不多,但是可以省一個\(while\)迴圈

由於\(n\)最大是\(10^7\),可以直接從\(2^{24}\)開始列舉(\(2^{24}\)是最貼近\(10^7\)且小於\(10^7\)\(2\)的非負整數次冪)

感謝來自\(Daddy\)的思路提供

上程式碼———

#include<iostream>
using namespace std;
int main()
{
	int n;
	cin>>n;
	if(n&1)
		cout<<-1;
	else
		for(int i=1<<24;i>0;i=i>>1)//i從2^24開始列舉,一直列舉到2^1
			if(n&i)//如果這位是1
				cout<<i<<" ";//輸出這一位的位權值
	return 0;
}

主要程式(從第10行到第12行)一共會執行24次

時間複雜度是\(O(1)\)

對於一些不大於\(10^7\)的大資料來說,比上面快兩倍

真是想不通比賽的時候自己為什麼要打暴搜

最後

看在我寫了\(3.5\)個小時的份上,不點個贊嗎?

完結撒花 (。・∀・)ノfafafa