1. 程式人生 > 其它 >【ACM】C++ 與 STL

【ACM】C++ 與 STL

2022LSNUACM_Winter_Week1Day1_Hoppz

C++ & STL

在標題後有 * 的建議看完後面的再回來閱讀。

1、常用庫函式

1.1、萬能頭

#include <bits/stdc++.h>

原理就是套娃。我自己寫的一個萬能頭。

// C
#ifndef _GLIBCXX_NO_ASSERT
#include <cassert>
#endif
#include <cctype>
#include <cerrno>
#include <cfloat>
#include <ciso646>
#include <climits>
#include <clocale>
#include <cmath>
#include <csetjmp>
#include <csignal>
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>

#if __cplusplus >= 201103L
#include <ccomplex>
#include <cfenv>
#include <cinttypes>
#include <cstdalign>
#include <cstdbool>
#include <cstdint>
#include <ctgmath>
#include <cwchar>
#include <cwctype>
#endif

// C++
#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>

#if __cplusplus >= 201103L
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <forward_list>
#include <future>
#include <initializer_list>
#include <mutex>
#include <random>
#include <ratio>
#include <regex>
#include <scoped_allocator>
#include <system_error>
#include <thread>
#include <tuple>
#include <typeindex>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#endif

這個是他的位置

因為 Visual Studio 的 C++庫中沒有宣告這個標頭檔案。所以我就直接弄了一個。他的作用就是幫你寫你常用的標頭檔案,你就不用寫這麼多了,但是一定要清楚那個函式在那個庫裡面!!! 因為萬一比賽的時候不支援標頭檔案,那不是隻有擺爛嗎。

1.2、資料型別的簡寫

typedef long long ll;

1.3、關於巨集

1.3.1、define

1.3.1、參考部落格

1.3.1.1、無參巨集

在巨集定義中沒有引數

#define inf 0x3f3f3f3f
#define L T[rt].l
#define Mid ((T[rt].l +T[rt].r)>>1)

無參巨集更像一種佔位替換,他相當於直接把原來程式碼中的巨集部分直接替換成後面的東西,僅作為字串儲存,並不會計算出他的結果

#define N 2+2
int main()
{
   int a=N*N;
   printf("%d",a);
}

以我們的想法,輸出的應該是 16,但是我們得到的是 8。他的計算邏輯是

\[a =2 + 2*2 + 2 \]

直接把 N 替換到N*N 中。

所以在一些數值計算的時候一定記得加括號,同時在define語句的最後沒有分號!!!

1.3.1.2、有參巨集(巨集函式)

C++語言允許巨集帶有引數。在巨集定義中的引數稱為形式引數,在巨集呼叫中的引數稱為實際引數。
對帶引數的巨集,在呼叫中,不僅要巨集展開,而且要用實參去代換形參

(有點類似printf 中的佔位符)。

#define max(x,y) (x)>(y)?(x):(y);	///  實現 max 函式

值得注意的是,如果我們寫一個函式形式的 max 函式是這樣的:

int Max(int x, int y){return x > y ? x : y;}

區別在於,沒有寫變數型別!

所以這樣的用法嚴格來說是不安全的,所以也僅僅只是我們比賽中可以這樣寫,但是專案中基本上不會這樣寫,更多的是看個人的習慣,但是一定要知道巨集是不安全的!所以有些文章再說不推薦比賽的時候寫巨集就是這個道理。但是最主要是還是個人習慣,沒必要爭個勝負。


更安全的寫法是用 template 後面不會擴充套件這個知識點,感興趣的可以自己下去研究。

貼一個楊會的巨集板子

//#pragma comment(linker,"/STACK:1024000000,1024000000")
//#pragma GCC optimize(2)
//#pragma GCC target ("sse4")
#include<bits/stdc++.h>
//typedef long long ll;
#define ull         unsigned long long
//#define int       __int128
#define int         long long
#define F           first
#define S           second
#define endl        "\n"//<<flush
#define eps         1e-6
#define base        131
#define lowbit(x)   (x&(-x))
#define db          double
#define PI          acos(-1.0)
#define inf         0x3f3f3f3f
#define MAXN        0x7fffffff
#define INF         0x3f3f3f3f3f3f3f3f
#define _for(i, x, y) for (int i = x; i <= y; i++)
#define for_(i, x, y) for (int i = x; i >= y; i--)
#define ferma(a,b)  pow(a%b,b-2)
#define mod(x)      (x%mod+mod)%mod
#define pb          push_back
#define decimal(x)  cout << fixed << setprecision(x);
#define all(x)      x.begin(),x.end()
#define rall(x)      x.rbegin(),x.rend()
#define memset(a,b) memset(a,b,sizeof(a));
#define IOS         ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
#ifndef ONLINE_JUDGE
#include "local.h"
#endif
template<typename T> inline T fetch(){T ret;cin >> ret;return ret;}
template<typename T> inline vector<T> fetch_vec(int sz){vector<T> ret(sz);for(auto& it: ret)cin >> it;return ret;}
template<typename T> inline void makeUnique(vector<T>& v){sort(v.begin(), v.end());v.erase(unique(v.begin(), v.end()), v.end());}
template<typename T> inline T max_(T a,T b){if(a>b)return a;return b;}
template<typename T> inline T min_(T a,T b){if(a<b)return a;return b;}
void file()
{
#ifdef ONLINE_JUDGE
#else
    freopen("D:/LSNU/codeforces/duipai/data.txt","r",stdin);
    freopen("D:/LSNU/codeforces/duipai/WA.txt","w",stdout);
#endif
}
void Match()
{
#ifdef ONLINE_JUDGE
#else
    Debug::Compare();
#endif // ONLINE_JUDGE
}
signed main()
{
    IOS;
    file();

    }
    Match();
    return 0;
}

1.3.2、const

一般與數值有關的,我個人更加習慣用 const 來定義,這樣資料的型別就得到了保障也更加安全。

const double eps = 1e-18;		/// 表示 0.000000000000000001 區別 1e18
const int N = 1e5 + 10;			/// 表示 100010 = 100000 + 10
const int maxp = 10;
const double pi = acos(-1);		/// 表示圓周率
const double inf = 1e14;		/// 1 後面跟上 14 個 0

同時在定義陣列大小的時候,必須是 const 型別的資料才可以定義為陣列的大小,常用形式為:

const int N = 2e5+10;
int a[N];

為什麼+10?訪問了未分配記憶體的空間!!!

在遍歷的時候,或者計算的時候有的時候因為邊界沒有考慮完備,會遍歷到沒有定義的空間,所以為了更好的容錯率一般多定義一點點。溢位的話,會報 Runtime Error 也就是直接卡死在那裡,我遇到的 Runtion Error \(99\%\)​​​ 的情況都是陣列越界。比如說

#include <bits/stdc++.h>
using namespace std;
vector<int> vec(100);
int main()
{
	cout << vec[100] << endl;
	return 0;
}

這裡就訪問了沒有分配的記憶體。

1.3、輸入,輸出函式

c語言 中我們一般用 scanf 來輸入, printf 來輸出,我們會引入 <stdio.h> 這個標頭檔案。

C++ 中我們一樣可以用這兩個來進行輸入以及輸出,但是我們更常用的是 cin 以及 cout 採用流式輸出輸出,需要引入 <iostream>

輸入

int a;
cin >>a;

輸出

cout <<a <<endl;
cout << a << "\n";	// 與上面的功能相同

>> 叫做輸入重定向,<<叫做輸出重定向。endl"\n"是一樣的。

格式化輸出

C語言 中我們可以用 %.10lf 來控制輸出的精度,cout 的格式化輸出較為麻煩一點

///非科學計數法輸出 
cout.setf(ios::fixed);
///設定輸出小數點 
cout << setprecision(4) << x <<endl;

一般遇到與精度有關的題的時候,我一般習慣不用流式輸入輸出,直接用標準輸入輸出。

1.3、與數學有關的庫函式

1.3.1、max 與 min

雖然只是一個簡單的比較大小但是都有非常非常多的寫法,上述的巨集定義,就不說了。

C++中我們可以直接用 <cmath> 庫中的 maxmin 函式。

#include <cmath>
#include <iostream>
using namespace std;
int main()
{
	cout << max(1, 2) << endl;
	return 0;
}

但是一般我們都直接用三目運算,a>b?a:b,或者寫成行內函數的形式

inline int Max(int a, int b) { return a > b ? a : b; }
int main()
{
	int a = 1, b = 2;
	cout << Max(1, b) << endl;
	// cout << (a > b ? a : b) << endl;
	return 0;
}

行內函數會在編譯的時候,在行內函數中的程式碼內嵌到原位置中,這樣就可以提升執行的速度。

如果要比賽大小的數大於2怎麼辦?

在C++11中,支援使用大括號把要取大小的數都框起來,類似初始化陣列的形式。

int ma = max({1,2,3,4,5,6})

如果我是要取得一個數組中的最大值和最小值呢?

1.3.2、max_element 與 min_element

要使用這兩個函式,要引入 <algorithm>這個庫,這兩個函式的作用就是返回一個指向陣列中的最大值或最小值的指標或迭代器,使用 * 取到地址、迭代器所對應的值。

int a[]={1,9,2,3,4};
int te = *max_element(a,a+5);
cout << te <<endl;

注意:諸如後面的 sortlower_bound,這種對 \(l,r\)​​​​ 區間進行操作的,都是在左閉右開 \([l,r)\)​​​ 的區間中進行操作,比如 $(a,a+5) $​​​ 實際上是 \((a+0,a+5)\)​​​​,我們知道在 C 語言中是從 \(0\)​​ 開始的的,所以在有 \(5\)​ 個數字的陣列中,最大下標是 \(4\)​​,雖然是寫的 \([0,5)\)​ 但是實際操作了的是 \([0,4]\)​ 。

1.3.3、pow 與 sqrt

首先,不要用 pow!!! 不要用 pow!!! 不要用 pow !!! 因為精度有問題。

pow(a,b)是用來求 a 的 b 次方等於多少,在校範圍中,精度還是準確的,但是稍微打一點點,不會爆double,long long 他就不準確了,卡了整個集訓隊1個小時的教訓。

這種誤差在不同的C++標準中,可能不同,比如 CodeBlocks 採用的C++標準就會出現,但是Visual StudioVisual Studio Code 都不會出現(這兩不是同一個軟體!!!)。

綜上,自己手寫個pow。


sqrt(a) 是用來一個數開二次方的結果,也就是取根號。

根據不用的資料型別可以選用:

  1. sqrt
  2. sqrtf
  3. sqrtl

關於精度

printf("%.30lf\n", sqrtl(2));
printf("%.30lf\n", sqrtf(2));

1.3.4 其他數學函式

/// 1、 三角函式
double sin(double);				// 正弦
double cos(double);				// 餘弦
double tan(double);				// 正切
/// 2 、反三角函式
double asin(double);			// 結果介於[-PI / 2, PI / 2]
double acos(double);			// 結果介於[0, PI]
double atan(double);			// 反正切(主值),結果介於[-PI / 2, PI / 2]
double atan2(double, double);	// 反正切(整圓值),結果介於[-PI, PI]
/// 3 、雙曲三角函式
double sinh(double);
double cosh(double);
double tanh(double);
/// 4 、指數與對數
double frexp(double value, int* exp);		// 這是一個將value值拆分成小數部分f和(以2為底的)指數部分exp,並返回小數部分f,即f * 2 ^ exp。其中f取值在0.5~1.0範圍或者0。
double ldexp(double x, int exp);			// 這個函式剛好跟上面那個frexp函式功能相反,它的返回值是x * 2 ^ exp
double modf(double value, double* iptr);	// 拆分value值,返回它的小數部分,iptr指向整數部分。
double log(double);							// 以e為底的對數
double log10(double);						// 以10為底的對數
double log2(double);						// 以2為底的對數
double pow(double x, double y);				// 計算x的y次冪
float powf(float x, float y);				// 功能與pow一致,只是輸入與輸出皆為浮點數
double exp(double);							// 求取自然數e的冪
/// 5 、取整
double ceil(double);						// 取上整,返回不比x小的最小整數
double floor(double);						// 取下整,返回不比x大的最大整數,即 高斯函式[x]
/// 6 、絕對值
int abs(int i);						// 求整型的絕對值
double fabs(double);				// 求實型的絕對值
double cabs(struct complex znum);	// 求複數的絕對值
/// 7 、標準化浮點數
double frexp(double f, int* p);		// 標準化浮點數,f = x * 2 ^ p,已知f求x, p(x介於[0.5, 1])
double ldexp(double x, int p);		// 與frexp相反,已知x, p求f
/// 8 、取整與取餘
double modf(double, double*);		// 將引數的整數部分通過指標回傳,返回小數部分
double fmod(double, double);		// 返回兩引數相除的餘數
/// 9 、其他
double hypot(double x, double y);					// 已知直角三角形兩個直角邊長度,求斜邊長度
double ldexp(double x, int exponent);				// 計算x* (2的exponent次冪)
double poly(double x, int degree, double coeffs[]); // 計算多項式

1.4、upper_bound(),lower_bound()

只能升序使用,降序無法使用,同時返回的是指標或迭代器,需要引入<algorithm>

	int a[] = { 1,2,4,4,6,7 };
	int loc1 = lower_bound(a, a + 6, 3) - a;
	int loc2 = lower_bound(a, a + 6, 4) - a;
	int loc3 = upper_bound(a, a + 6, 3) - a;
	auto loc4 = upper_bound(a, a + 6, 3);
	cout << loc1 << endl;
	cout << loc2 << endl;
	cout << loc3 << endl;
	cout << *loc4 << endl;
  • lower_bound(l,r,val) 返回第一個等於 val 值的地址,如果沒有就返回第一個大於val的地址。
  • upper_bound(l,r,val) 返回第一個大於 val值的地址。

我們可以用最後返回的地址,減去首地址的方式來得到這個元素的下標,或者直接用解引用運算子來獲取這個地址所代表的值。

1.5、sort()

我們最常用的基本上就是 sort 他的作用就是排序,但是內部實現是非常複雜的,他會根據不用的資料量來採用不用的排序演算法,他的期望時間複雜度是非常優秀的,需引入<algorithm>

  1. sort(a,a+n) 預設升序
  2. sort(a,a+n,less<int>()) 升序
  3. sort(a,a+n,greater<int>()) 降序

我們也可以自己寫一個排列升序的比較函式

bool cmp(int a, int b){return a > b;}
int main()
{
	int a[] = { 1,2,4,4,6,7 };
	sort(a, a + 6, cmp);
	cout << a[0] << endl;
}

同時對於一個結構體,我們也可以安不同的標準來進行排序。

在一次考試中,我想以數學為第一排序,如果數學相同,就以語文為第二排序,如果語文相同就以英文為第三排序,排序標準是成績高的排在前面。

struct Node
{
	int ma;
	int ch;
	int en;
	//bool operator < (Node b) {
	//	if (ma == b.ma) {
	//		if (ch == b.ch) {
	//			return en > b.en;
	//		}
	//		return ch > b.ch;
	//	};
	//	return ma > b.ma;
	//}
};
bool cmp(Node a, Node b)
{
	if (a.ma == b.ma) {
		if (a.ch == b.ch) {
			return a.en > b.en;
		}
		return a.ch > b.ch;
	};
	return a.ma > b.ma;
}
int main()
{
	Node stu[5];
	stu[0] = { 99,99,2 };
	stu[1] = { 88,100,100 };
	stu[2] = { 99,2,99 };
	stu[3] = { 99,99,100 };
	stu[4] = { 100,100,100 };
	sort(stu, stu + 5,cmp);
	/*
	* 或者我們把註釋的解除,直接用
	* sort(stu,stu+5);
	* 效果是一樣的
	*/
	for (int i = 0; i < 5; i++) {
		cout << stu[i].ma << ' ' << stu[i].ch << ' ' << stu[i].en << endl;
	}
}

1.6、unique()

首先,這個函式只能對相同元素在並鄰在一塊的序列進行去重。不能對相同元素七零八落地分佈的一般序列進行去重,可以對一般陣列進行排序後再用 unique 實現去重目的即可,因為排好序的的序列裡面相同元素一定儲存在連續的地址塊。

他的作用是把不重複的元素移到前面來,需要引入<algorithm>,比如說

\(\{1,2,2,3,5,5,6,7\}\)​ 在進行 unique 之後,就會變成

\(\{1,2,3,5,6,7,6,7\}\)​​​最後返回一個指向第二個 6 的指標(假定為 p),在這個指標之後的所有元素都是不需要的,也就是說,我們的新的去重後的陣列變成了 \([a,p)\)​​​ (a,p均為指標)

int a[] = { 1,2,2,3,5,5,6,7 };
unique(a, a + 8);
for (int i = 0; i < 8; i++) cout << a[i] << ' '; cout << endl;

例1

1.7、memset()

需引入<cstring>這個函式的作用是把一個數組中的一個區間全部變成一個值,但是這個值只能是一個兩位重複的16進位制數 比如說 0x131313130x3f3f3f3f0x00000000​​​,但是輸入的時候只用輸入 0x13

memset(a,0x13,sizeof a) 第一個引數是輸出需要賦值的首地址,第二個是值,第三個是需要初始化的範圍,從首地址開始後面的多大的範圍內。

memset(a, 0x13, sizeof a);
cout << a[0] << ' ' << 0x13131313 << ' ' << 0x13 << endl;

所以,想要賦值為 1 的話,是不行的。

memset(a, 0x1, sizeof a);
cout << a[0] << ' ' << 0x01010101 << ' ' << 0x1 << endl;

但是很多時候其實寫一個 for迴圈就好了,這個只是有的時候比較方便,比如說 0,和 0x3f3f3f3f

1.8、reverse()

需引入<algorithm> ,作用是把一個數組中的一個區間反轉。

	int a[] = {1,2,3};
	reverse(a, a + 3);
	for (int i = 0; i < 3; i++)cout << a[i] << ' '; cout << endl;

2、string

2.1、什麼是 string

string 中文名稱 字串 ,他的底層就是 char [] ,但是為了使字串的操作更加方便,專門衍生出一個新的資料型別 string ,但是他自帶了很多的方法,便於我們對字串進行操作。

2.2、讀入與輸出 string

string s;
cin >> s;		// 不能讀入一行中的空格
getline(cin,s);	// 能讀入空格
cout <<s <<endl;

2.3、字典序與大小比較

我們按照 ASCII碼定義字串的大小;

字典序,就是按照字典(ASCII碼)中出現的先後順序進行排序。比如說:

"cba" 按照字典序排列後 變成 "abc"


這個對於一個字串內部的字典序排序,那麼對於一個字串陣列,我們安裝字典序排列是怎麼樣的呢?

對於字串,先比較首字元,首字母小的排前面,如果首字母相同,再比較第二個字元,以此類推。

比如說: "abc""cbb""abd""cbbb" 排列後的結果為:

"abc""abd""cbb"cbbb

例2

2.4、一些作用於 string 的函式

  1. 交換兩個字串
	string a = "233", b = "322";
	swap(a, b);
	cout<< a << endl;
  1. 新增,賦值字串
	string s = "";
	s += "a";
	cout << s << endl;
	s += "b";
	cout << s << endl;
	s.append("233");
	cout << s << endl;
	s.push_back('s');	// 注意和 append 的區別
	cout << s << endl;
  1. 插入字串
	string s = "13";
	s.insert(1, "22");
	cout << s << endl;
  1. 刪除字串
	string s = "1223";
	s.erase(1,2);
	cout << s << endl;
  1. 比較字串的內容(字典序大小)
	string a = "abc", b = "abd";
	if (a > b)cout << 1;
	else if (a < b) cout << 2;
	else if (a == b) cout << 3;
	else if (a >= b) cout << 4;
	else if (a <= b) cout << 4;
	else if( a.empty() ) cout << 5;	// 如果字串為空返回 true,反之 false
  1. 返回字串大小(長度)
	string a = "abc";
	cout << a.size() << ' ' << a.length() << endl;
  1. 取子串
	string a = "abcdef";
	//  從下標 1 開始,後面 3 位
	string sub = a.substr(1, 3);
	cout << sub << endl;
  1. 搜尋某子串或字元
	string a = "abcdef";
	//  從下標 1 開始,後面 3 位
	if (a.find("h") == string::npos) {
		cout << "Not Find" << endl;
	}
	if (a.find("h") == -1) {
		cout << "Not Find" << endl;
	}
	cout << a.find("cd") << endl;

找到的話,返回起始子串的起始下標,未找到返回 string::npos 其實就是 -1

3、C++特性

3.1、解綁

ios::sync_with_stdio(false);

C++ 為了保證程式在使用了 printfcout 時不發生衝突,進行了相容性處理。cincout之所以效率低,就是因為先把要輸出的東西存入緩衝區,再輸出,導致效率降低。因此,很多人都會選擇使用 scanfprintf 以加快執行速度。如果我們不同時使用這兩種輸出方法的話,為了提高執行速度,我們可以將其解除繫結,這樣做了之後就不能同時混用cout 和 printf

cin.tie(0);cout.tie(0);

同時,C++ 在預設的情況下 cincout 也是繫結的,每次執行 << 操作符的時候都要呼叫 flush,這樣會增加 I/O 負擔。可以通過tie(0)(0表示NULL)來解除 cincout 的繫結,進一步加快執行效率。

切記,解綁後不要在使用 scanf 和 printf 函式 !!!

3.2、auto 資料型別*

這個東西可以自動的把定義變數變成賦值變數的型別。這裡我們用<typeinfo>來幫助我們輸出資料的型別。

#include <typeinfo>
#include <bits/stdc++.h>
using namespace std;
int main()
{
	auto te = 3.1415926;
	cout << typeid(te).name() << endl;
	return 0;
}

這樣有很多很多的好處,比如說後面我們要學的STL容器 中的迭代器,每個容器的迭代器,要寫很長,我們直接一個 auto 就可以搞定,比如說:

#include <typeinfo>
#include <bits/stdc++.h>
using namespace std;
int main()
{
	vector<int> vec;
	vec.push_back(1);
	vector<int>::iterator it = vec.begin();
	cout << *it << endl;
	/// 對比
	auto itt = vec.begin();
	cout << *itt << endl;
	cout << typeid(it).name() << endl;
	cout << typeid(itt).name() << endl;
	return 0;
}

還有就是我們後面在學佇列的時候也會常用

    queue<pair<int, int>> que;
    while (!que.empty()) {
        auto no = que.front();
        que.pop();
        ...
    }

這樣也更加快捷,但是這樣的話,程式碼的可讀性會降低!!!

3.3、加強 for 迴圈*

加強 for 迴圈,在其他的語言中如 JavaJavaScirpt 中叫做 forEach 當然最新的C++版本中也引入了 for_each函式。他依賴於上面的 auto 資料型別。

3.3.1、容器中的單變數應用

    vector<int> vec(10,1);
    for (auto it : vec) {
        it = 3;
        cout << it << endl;
    }

我們執行後可以發現,這裡的值輸出的任然是 1。所以單一的用 auto it 來遍歷是無法修改原始容器中的資料的。要使用 & 取地址符,就像在函式中傳遞引數一樣。

    vector<int> vec(10,1);
    for (auto &it : vec) {
        it = 3;
        cout << it << endl;
    }

3.3.2、容器中多變數的應用

    map<string, int> mp;
    mp["a"] = 1;
    mp["b"] = 2;
    for (auto it : mp) {
    	cout << it.first << ' ' << it.second << endl;
    }
    for (auto [a, b] : mp) {
    	cout << a << ' ' << b << endl;
    }

後面的那種形式是 C++17 的新特性,如果用的C++版本低於 C++17是無法使用的!!!

4、C++ STL

4.1、什麼是STL

我們先來看看一個C++程式碼

#include <iostream>
#include <iomanip>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

struct Point
{
    double x,y;

    Point(double _x,double _y){
        x = _x,y = _y;
    }

    Point(){}
};

int main()
{
    vector<Point> vec;

    vec.push_back({1,2});
    vec.push_back(Point(3,4.34));
    vec.push_back({1,231});
    vec.push_back({2131,2});
    vec.push_back({12341,213});

    //vec.push_back(make_pair(2,3));


    ///讀入

    double a,b;
    cin >>a>> b;
    vec.push_back(Point(a,b));


    for(vector<Point>::iterator it = vec.begin(); it != vec.end() ; it ++){
        cout << (*it).x << " " << (*it).y << endl;
    }

    cout << endl;
    cout.setf(ios::fixed);

    reverse(vec.begin(),vec.end());
    //reverse(a,a+n);


    for(auto it : vec){
        cout << setprecision(2)  << it.x << "-" << it.y << endl;
    }

    return 0;
}

C++標準

首先需要介紹的是 C++ 本身的版本。由於 C++ 本身只是一門語言,而不同的編譯器對 C++ 的實現方法各不一致,因此需要標準化約束編譯器的實現,使得 C++ 程式碼在不同的編譯器下表現一致。

標準模板庫 STL

標準模板庫(英文Standard Template Library縮寫STL),是一個C++軟體庫,大量影響了C++標準程式庫但並非是其的一部分。其中包含4個元件,分別為演算法容器函式迭代器

模板是C++程式設計語言中的一個重要特徵,而標準模板庫正是基於此特徵。標準模板庫使得C++程式語言在有了同Java一樣強大的類庫的同時,保有了更大的可擴充套件性

STL在資料上執行的操作與要執行操作的資料分開,分別以如下概念指代:

  • 容器:包含、放置資料的地方。
  • 迭代器:在容器中指出一個位置、或成對使用以劃定一個區域,用來限定操作所涉及到的資料範圍。
  • 演算法:要執行的操作。

4.2、容器


在電腦科學中,容器以一種遵循特定訪問規則的系統的方法來儲存物件。

思考:我們之前的儲存物件有些什麼? 陣列, 單個元素。

4.2.1、vector

std::vector 是 STL 提供的 記憶體連續的可變長度 的陣列(亦稱列表)資料結構。能夠提供線性複雜度的插入和刪除,以及常數複雜度的隨機訪問。

  1. 為什麼使用vector?

    • vector 重寫了比較運算子和賦值運算子
    • vector 的可變長
    • vector的記憶體是動態分配
  2. 元素訪問

    vector 提供瞭如下幾種方法進行元素訪問

    • at()

    v.at(pos) 返回容器中下標為 pos 的引用。如果陣列越界丟擲 std::out_of_range 型別的異常。

    • operator[]

    v[pos] 返回容器中下標為 pos 的引用。不執行越界檢查。

    • front()

    v.front() 返回首元素的引用。

    • back()

    v.back() 返回末尾元素的引用。

    • data()

    v.data() 返回指向陣列第一個元素的指標。

  3. vector中的迭代器

  4. 與長度有關的 函式

    • empty() 返回一個 bool 值,即 v.begin() == v.end()true 為空,false 為非空。
    • size() 返回容器長度(元素數量),即 std::distance(v.begin(), v.end())
  5. 元素的刪除及修改
    - clear() 清除所有元素
    - insert() 支援在某個迭代器位置插入元素、可以插入多個。複雜度與 pos 距離末尾長度成線性而非常數的
    - erase() 刪除某個迭代器或者區間的元素,返回最後被刪除的迭代器。複雜度與 insert 一致。
    - push_back() 在末尾插入一個元素,均攤複雜度為 常數,最壞為線性複雜度。
    - pop_back() 刪除末尾元素,常數複雜度。

4.2.2、deque

基本上與vector一直,唯一需要注意的是,在佇列的首,尾插入刪除元素為常數級

4.2.3、set

set 是關聯容器,含有鍵值型別物件的已排序集搜尋移除插入擁有對數複雜度\(nlog(n)\))。set 內部通常採用紅黑樹實現。平衡二叉樹的特性使得 set 非常適合處理需要同時兼顧查詢、插入與刪除的情況。

  1. 插入與刪除

    • insert(x) 當容器中沒有等價元素的時候,將元素 x 插入到 set 中。
    • erase(x) 刪除值為 x 的 所有 元素,返回刪除元素的個數。( 與後面的multiset 有區別)
    • erase(pos) 刪除迭代器為 pos 的元素,要求迭代器必須合法。
    • erase(first,last) 刪除迭代器在 範圍內的所有元素。
    • clear() 清空 set
  2. 查詢操作

    • count(x) 返回 set為 x 的元素數量。
    • find(x)set 記憶體在為 x 的元素時會返回該元素的迭代器,否則返回 end()
    • lower_bound(x) 返回指向首個不小於給定鍵的元素的迭代器。如果不存在這樣 的元素,返回 end()
    • upper_bound(x) 返回指向首個大於給定鍵的元素的迭代器。如果不存在這樣的元素,返回 end()
    • empty() 返回容器是否為空。
    • size() 返回容器內元素個數。
  1. set 在貪心中使用

在貪心演算法中經常會需要出現類似 找出並刪除最小的大於等於某個值的元素。這種操作能輕鬆地通過 set 來完成。

```c++
// 現存可用的元素
set<int> se;
// 需要大於等於的值
int x;

// 查詢最小的大於等於x的元素
set<int>::iterator it = se.lower_bound(x);
if (it == se.end()) {
  // 不存在這樣的元素,則進行相應操作……
} else {
  // 找到了這樣的元素,將其從現存可用元素中移除
  se.erase(it);
  // 進行相應操作……
}
```

例1

Hyperset - CodeForces 1287B


Bees Alice and Alesya gave beekeeper Polina famous card game "Set" as a Christmas present. The deck consists of cards that vary in four features across three options for each kind of feature: number of shapes, shape, shading, and color. In this game, some combinations of three cards are said to make up a set. For every feature — color, number, shape, and shading — the three cards must display that feature as either all the same, or pairwise different. The picture below shows how sets look.

Polina came up with a new game called "Hyperset". In her game, there are nn cards with kk features, each feature has three possible values: "S", "E", or "T". The original "Set" game can be viewed as "Hyperset" with $ k=4$.

Similarly to the original game, three cards form a set, if all features are the same for all cards or are pairwise different. The goal of the game is to compute the number of ways to choose three cards that form a set.

Unfortunately, winter holidays have come to an end, and it's time for Polina to go to school. Help Polina find the number of sets among the cards lying on the table.

Input

The first line of each test contains two integers nn and kk (\(1≤n≤1500\), \(1≤k≤30\) — number of cards and number of features.

Each of the following nn lines contains a card description: a string consisting of kk letters "S", "E", "T". The ii-th character of this string decribes the ii-th feature of that card. All cards are distinct.

Output

Output a single integer — the number of ways to choose three cards that form a set.

Examples

input

3 3
SET
ETS
TSE

output

1

input

3 4
SETE
ETSE
TSES

output

0

input

5 4
SETT
TEST
EEET
ESTE
STES

output

2

Note

In the third example test, these two triples of cards are sets:

  1. "SETT", "TEST", "EEET"
  2. "TEST", "ESTE", "STES"

4.2.4、map

  1. 什麼是鍵值對

    在將後者與前者結合起來就是map了

  2. map

    map 是有序鍵值對容器,它的元素的鍵是唯一的。搜尋、移除和插入操作擁有對數複雜度map 通常實現為紅黑樹。

    你可能需要儲存一些鍵值對,例如儲存學生姓名對應的分數:Tom 0Bob 100Alan 100。但是由於陣列下標只能為非負整數,所以無法用姓名作為下標來儲存,這個時候最簡單的辦法就是使用 STL 中的 map 了!

  3. 插入與刪除操作

    • 可以直接通過下標訪問來進行查詢或插入操作。例如 mp["Alan"]=100
    • 通過向 map 中插入一個型別為 pair<Key, T> 的值可以達到插入元素的目的,例如 mp.insert(pair<string,int>("Alan",100));或者mp.insert({key,value})
    • erase(key) 函式會刪除鍵為 key所有 元素。返回值為刪除元素的數量。
    • erase(pos): 刪除迭代器為 pos 的元素,要求迭代器必須合法。
    • erase(first,last): 刪除迭代器在 \([first,last)]\) 範圍內的所有元素。
    • clear() 函式會清空整個容器。

    下標訪問中的注意事項


    在利用下標訪問 map 中的某個元素時,如果 map不存在相應鍵的元素,會自動在 map 中插入一個新元素(一般都是用次方法插入),並將其值設定為預設值(對於整數,值為零;對於有預設建構函式的型別,會呼叫預設建構函式進行初始化)。

    當下標訪問操作過於頻繁時,容器中會出現大量無意義元素,影響 map 的效率。因此一般情況下推薦使用 find() 函式來尋找特定鍵的元素。

  4. 查詢操作

    • count(x): 返回容器內鍵為 x 的元素數量。複雜度為 (關於容器大小對數複雜度,加上匹配個數)。
    • find(x): 若容器記憶體在鍵為 x 的元素,會返回該元素的迭代器;否則返回 end()
    • lower_bound(x): 返回指向首個不小於給定鍵的元素的迭代器。
    • upper_bound(x): 返回指向首個大於給定鍵的元素的迭代器。若容器內所有元素均小於或等於給定鍵,返回 end()
    • empty(): 返回容器是否為空。
    • size(): 返回容器內元素個數。

例2

Dyson Box


A Dyson Sphere is a hypothetical megastructure that completely encompasses a star and captures a large percentage of its power output. The concept is a thought experiment that attempts to explain how a spacefaring civilization would meet its energy requirements once those requirements exceed what can be generated from the home planet’s resources alone. Only a tiny fraction of a star’s energy emissions reach the surface of any orbiting planet. Building structures encircling a star would enable a civilization to harvest far more energy.

One day, Moca has another idea for a thought experiment. Assume there is a special box called Dyson Box. The gravitational field in this box is unstable. The direction of the gravity inside the box can not be determined until it is opened.

The inside of the box can be formed as a 2-dimensional grid, while the bottom left corner’s coordinate is $ (0, 0)$ and the upper right corner’s coordinate is (\(2 · 10^5,2 · 10^5\) ). There will be n events. In the i-th event, a new cube will appear, whose upper right corner’s coordinate is (\(x_i , y_i\)) and bottom left corner’s coordinate is (\(x_i − 1, y_i − 1\)).

There are two directions of gravity in the box, vertical and horizontal. Suppose Moca opens the box after the i-th event. In that case, she has $ \frac{1} {2}$ probability of seeing the direction of the gravity inside the box is vertical, and the other $ \frac{1} {2}$ probability is horizontal. And then, she will measure the total length of the outline of all the cubes. If the direction of gravity is horizontal, all the cubes inside will move horizontally to the left under its influence. Similarly, vertical gravity will cause all the cubes to move downward.

Moca hates probability, so that she is asking for your help. If you have known the coordinates of all the cubes in chronological order, can you calculate the total length of these two cases after each event?

Input

The first line contains one integer n (1 ≤ n ≤ 2 · 105 ) – the number of cubes. Each of the following n lines describes a cube with two integers xi , yi (1 ≤ xi , yi ≤ 2 · 105 ). It is guaranteed that no two cubes have the same coordinates.

Output

For each of the n cubes, print one line containing two integers – two answers when the the direction of gravity is vertical and horizontal.

Note

In the only example, the inside of the box is as below, and the bold lines mark the outline of all the cubes. After the 1-st event:

4.3、Struct 重寫 & 在 STL容器中的應用


#include <iostream>
#include <iomanip>
#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;

struct Point
{
    double x,y;

    Point(double _x,double _y){
        x = _x,y = _y;
    }
    bool operator < (const Point& b) const{
        if( x == b.x ){
            return y< b.y;
        }
        return x < b.x;
    }

    Point(){}
};

int main()
{
    set<Point> se;


    se.insert({3,24325});
    se.insert({283,25});
    se.insert({21453,2435});
    se.insert({213,35});
    
    for(auto it :se){
        cout << it.x << ' ' <<it .y <<endl;
    }

    return 0;
}

4.4、例題

4.4.1、體測

原題連結 :A-Two Point Removal_第 46 屆 ICPC 國際大學生程式設計競賽亞洲區域賽(上海)熱身賽

4.4.2、Problem - A - Codeforces

4.4.3、Problem - C - Codeforces

5、習題

5.1、Problem - A - Codeforces

#include <bits/stdc++.h>
using namespace std;

void solve()
{
	string s;
	cin >> s;
	if (s.length() & 1) {
		puts("NO");
		return;
	}
	string a = s.substr(0, (s.length() >> 1));
	string b = s.substr((s.length() >> 1), (s.length() >> 1));
	if (a == b) puts("YES");
	else puts("NO");
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);

	int t;
	cin >> t;
	while (t--) {
		solve();
	}
}

5.2、Problem - 1617A - Codeforces

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;


void solve()
{
	string a, b,ans="";
	cin >> a >> b;
	sort(a.begin(), a.end());
	int na = 0, nb = 0, nc = 0,loc=-1;
	for (int i = 0; i < a.length(); i++) {
		if (a[i] == 'a') na++;
		else if (a[i] == 'b') nb++;
		else if (a[i] == 'c') nc++;
		else {
			loc = i;
			break;
		}
	}

	if (b == "abc" && na && nb && nc) {
		for (int i = 0; i < na; i++) {
			cout << "a";
		}
		for (int i = 0; i < nc; i++) {
			cout << "c";
		}
		for (int i = 0; i < nb; i++) {
			cout << "b";
		}
		if (loc != -1) {
			for (int i = loc; i < a.length(); i++) {
				cout << a[i];
			}
		}
		cout << endl;
	}
	else {
		cout << a << endl;
	}

	
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);

	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

5.3、Problem - A - Codeforces

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;


void solve()
{
	string a, b,ans="";
	cin >> a >> b;
	sort(a.begin(), a.end());
	int na = 0, nb = 0, nc = 0,loc=-1;
	for (int i = 0; i < a.length(); i++) {
		if (a[i] == 'a') na++;
		else if (a[i] == 'b') nb++;
		else if (a[i] == 'c') nc++;
		else {
			loc = i;
			break;
		}
	}

	if (b == "abc" && na && nb && nc) {
		for (int i = 0; i < na; i++) {
			cout << "a";
		}
		for (int i = 0; i < nc; i++) {
			cout << "c";
		}
		for (int i = 0; i < nb; i++) {
			cout << "b";
		}
		if (loc != -1) {
			for (int i = loc; i < a.length(); i++) {
				cout << a[i];
			}
		}
		cout << endl;
	}
	else {
		cout << a << endl;
	}

	
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);

	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

5.4、Problem - 1623B - Codeforces

程式碼

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
struct Node
{
	int l, r, len, id,ans;
}a[1010];

bool cmp1(Node a, Node b)
{
	if (a.len == b.len) return a.l < b.l;
	return a.len < b.len;
}
bool cmp2(Node a, Node b)
{
	return a.id < b.id;
}
bool st[1010];
void solve()
{
	int n;
	cin >> n;
	memset(st, 0, sizeof st);
	for (int i = 0; i < n; i++) {
		cin >> a[i].l >> a[i].r;
		a[i].len = a[i].r - a[i].l + 1;
		a[i].id = i;
	}
	sort(a, a + n, cmp1);
	for (int i = 0; i < n; i++) {
		for (int j = a[i].l; j <= a[i].r; j++) {
			if (st[j] == 0) {
				st[j] = 1;
				a[i].ans = j;
				break;
			}
		}
	}
	//sort(a, a + n, cmp2);
	for (int i = 0; i < n; i++) {
		cout << a[i].l << ' ' << a[i].r << ' ' << a[i].ans << endl;
	}cout << endl;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);

	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

5.5、Problem - C - Codeforces

#include <bits/stdc++.h>
using namespace std;
int main() {
    int c;
    cin >> c;
    for (int i = 0; i < c; i++) {
        int n, k;
        cin >> n >> k;
        vector<int> x(n);
        for (int j = 0; j < n; j++) {
            cin >> x[j];
        }
        vector<int> a, b;
        for (int j = 0; j < n; j++) {
            if (x[j] < 0) {
                a.push_back(-x[j]);
            }
            if (x[j] > 0) {
                b.push_back(x[j]);
            }
        }
        sort(a.begin(), a.end());
        sort(b.begin(), b.end());
        int cnta = a.size();
        int cntb = b.size();
        long long ans = 0;
        for (int j = cnta - 1; j >= 0; j -= k) {
            ans += a[j] * 2;
        }
        for (int j = cntb - 1; j >= 0; j -= k) {
            ans += b[j] * 2;
        }
        int mx = 0;
        if (cnta > 0) {
            mx = max(mx, a[cnta - 1]);
        }
        if (cntb > 0) {
            mx = max(mx, b[cntb - 1]);
        }
        ans -= mx;
        cout << ans << endl;
    }
}

5.6、Expression Evaluation Error

題意: 給一個 \(n , s\)\(n\) 為一個數列的和,\(s\)為這個數列中數的個數,現在把 \(n\) 拆分成 \(s\) 個數,我們把這 s 個數視為 11 進位制 求和,要使得這個11進位制的數最大

題解: 對於10拆成9 + 1 ,會發現損失了1,所以我們要儘可能的保留最高位的數,並且要保證,之後可以拆成1 n-(s-i)就是在做這一步

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int N = 1e5 +10;
 
int mypow(int x)
{
    int res = 1;
    for(int i = 0 ; i < x ; i++){
        res *= 10;
    }
    return res;
}

void solve()
{
    int n,s;
    cin >> n >> s;
    for(int i= 1; i < s ; i++){
        /// n-(s-i) 確保了最後可以補 1
        int x = mypow(int(log10(n - (s - i))) );
        cout << x << " ";
        n -= x;
    }
    cout << n <<endl;
}
 
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
 
    int t;
    cin >>t;
    while(t--){
        solve();
    }
 
    return 0;
}