洛谷 P1161 開燈

木槐muhuai發表於2024-07-25

目錄
  • 題目 - 開燈
    • 題目描述
    • 輸入格式
    • 輸出格式
    • 樣例 #1
      • 樣例輸入 #1
      • 樣例輸出 #1
    • 提示
  • AC CODE
    • 思路
    • AC CODE
        • C++
  • 說明
        • 使用 Map 做資料標記,會出現 TEL
        • 向下取整
        • <math.h> 中常用的函式

題目 - 開燈

題目描述

在一條無限長的路上,有一排無限長的路燈,編號為 1 , 2 , 3 , 4 , ······。

每一盞燈只有兩種可能的狀態,開或者關。如果按一下某一盞燈的開關,那麼這盞燈的狀態將發生改變。如果原來是開,將變成關。如果原來是關,將變成開。

在剛開始的時候,所有的燈都是關的。小明每次可以進行如下的操作:

指定兩個數, a , t ( a 為實數, t 為正整數)。將編號為 ⌊a⌋ , ⌊2×a⌋ , ⌊3×a⌋ , … , ⌊t×a⌋ 的燈的開關各按一次。其中 ⌊k⌋ 表示實數 k 的整數部分。

在小明進行了 n 次操作後,小明突然發現,這個時候只有一盞燈是開的,小明很想知道這盞燈的編號,可是這盞燈離小明太遠了,小明看不清編號是多少。

幸好,小明還記得之前的 n 次操作。於是小明找到了你,你能幫他計算出這盞開著的燈的編號嗎?

輸入格式

第一行一個正整數 n ,表示 n 次操作。

接下來有 n 行,每行兩個數, ai , ti 。其中 ai 是實數,小數點後一定有 6 位, ti 是正整數。

輸出格式

僅一個正整數,那盞開著的燈的編號。

樣例 #1

樣例輸入 #1

3
1.618034 13
2.618034 7
1.000000 21

樣例輸出 #1

20

提示

記 T 為對 ti 求和的值,

  • 對於 30% 的資料,滿足 T < 1000 ;
  • 對於 80% 的資料,滿足 T < 200000 ;
  • 對於 100% 的資料,滿足 T < 2000000 ;
  • 對於 100% 的資料,滿足 n < 5000 , 1 < ai <1000 , 1 < ti < T 。

資料保證,在經過 n 次操作後,有且只有一盞燈是開的,不必判錯。而且對於所有的 i 來說, ti * ai 的最大值不超過 2000000 。

AC CODE

思路

  • 題目的資料量 > 2e6 ,使用陣列(array)是裝不下的,而且最後遍歷陣列的時間複雜度也非常高
  • 因此總體思路是,使用某個結構,標記每次操作後出現變化的燈

使用 Map

  • 每次進行 map.find 操作,如果該燈已被標記,則翻轉燈的狀態,如果該燈未被標記,則將其打上 "已被點亮" 的標記
  • 遍歷 map 中的元素,其中標記為 "點亮" 的燈為結果

使用 Set

  • 設定點亮的燈被放入 set 中,那麼每次對被選中的燈進行 set.find 操作,如果 find 成功,表示燈需要被熄滅,則進行 set.erase 操作,如果 find 失敗,則表示該燈需要被點亮,進行 set.insert 操作
  • 最後 set 中只會剩下被點亮的燈,在進行結果遍歷時會更快

AC CODE

C++

#include <iostream>
#include <set>

using namespace std;

int main()
{
    int t;
    cin >> t;
    set<int> opare_set;
    while (t != 0)
    {
        double num;
        int k;
        cin >> num >> k;
        for (int i = 1; i <= k; i++)
        {
            int temp = (int)(1.0 * num * i);
            if (opare_set.find(temp) == opare_set.end())
            {
                opare_set.insert(temp);
            }
            else
            {
                opare_set.erase(temp);
            }
        }
        t--;
    }
    for (auto it : opare_set) {
        cout << it << endl;
    }
    return 0;
}

說明

使用 Map 做資料標記,會出現 TEL

  • 因為 set 的查詢效率更高,它的增刪改查時間複雜度都為 logN

    set 會對集合中的元素進行排序,set 的底層資料結構是紅黑樹(平衡二叉樹),因此查詢效率更高

  • 最後 set 中只會剩下被點亮的燈,在進行結果遍歷時會更快

  • 和 Java 中 Set 容器的底層實現類似,Set 是一組只用 key 值的 Map 結構

在此附上使用 Map 之後 TEL 的程式碼

#include<bits/stdc++.h>
using namespace std;
int lowerN(double n) // 向下取整函式 
{
	return floor(n);
}
int main()
{
	int k;
	cin>>k;
	map <int,bool> flag; // map記錄被改變狀態的燈 
	for(int h=0;h<k;h++)
	{
		double a;
		int t;
		cin>>a>>t;
		for(int i=1;i<=t;i++)
		{
			int temp=1.0*a*i;
			temp=lowerN(temp);
			//迭代器 記錄燈是否改變狀態 
			map <int,bool>::iterator iterflag; 
			iterflag=flag.find(temp);
			if(iterflag==flag.end())
			{
				flag.insert(pair<int,bool>(temp,true));
			} 
			else
			{
				flag[temp]=!flag[temp];
			}
		}
	} 
	int anser;
	map <int,bool>::iterator iteranser;
	for(iteranser=flag.begin();iteranser!=flag.end();iteranser++)
	{
		if(iteranser->second==true)
		{
			anser=iteranser->first;
			break;
		}
	}
	cout<<anser<<endl;
	return 0;
} 

向下取整

  • 在 C/C++/Java 中,使用 (int) 等進行強制型別轉換,計算機會採用丟失精度的方式進行強轉,得到的結果就是保留了數值的整數位
  • 可以使用 <math.h> 標頭檔案中的 floor 函式完成取整操作

<math.h> 中常用的函式

參考自以下文章

math.h標頭檔案下的常用函式!取整,取絕對值,四捨五入......看這一篇就夠了!

math.h - 百度百科

取整函式

  • floor(double x):向下取整函式,標準定義為取比 x 小的最大整數,因此 floor(-2.3) = -3
  • ceil(double x):向上取整函式,標準定義為取比 x 大的最小整數,因此 ceil(-2.3) = -2

取絕對值

  • abs(int x):對整型取絕對值
  • fabs(double x):對浮點型取絕對值
  • labs(long x):對長整型取絕對值

四捨五入

  • round(double x):四捨五入函式

運算

  • pow(double x, double y):冪函式,求 x 的 y 次方,當 y = 0.5,就是對 x 求平方根
  • log(double x):對數函式,求以 e 為底數 x 的對數
  • sqrt(double x):對 x 開平方函式,得到一個大於 0 的結果