acwing算法基礎課整理ACM模板


ACM模板


目錄

基礎算法

排序

快速排序

#include<iostream>
using namespace std;

const int N = 1e6 + 10;
int q[N];
int n;

void quick_sort(int q[], int l, int r) {
    if(l >= r) return;
    int i = l-1, j = r+1, x = q[(l+r)>>1]; // 這里x選取的時候不要使用q[l],不然容易超時
    while(i < j) {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
    
        if(i < j)  swap(q[i], q[j]); // 如果i < j 則交換
    }
    quick_sort(q, l, j);
    quick_sort(q, j+1, r);
    
}

int main() {

    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &q[i]);
    
    quick_sort(q, 0, n-1);
    
    for(int i = 0; i < n; i++) printf("%d ", q[i]);
    
    return 0;
}

歸並排序

#include<iostream>
using namespace std;
const int N = 1e5+10;
int q[N], tmp[N];
int n;

void merge_sort(int q[], int l, int r){
    if(l >= r) return; // 只有一個元素,不必排序,直接返回
    
    int mid = l + r >> 1 ;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);
    
    // 合並左右兩段
    int i = l, j = mid+1, k = 0; // i = l, j = mid+1
    while(i <= mid && j <= r) {
        if(q[i] <= q[j]) tmp[k ++] = q[i ++];
        else             tmp[k ++] = q[j ++];
    }
    while(i <= mid) tmp[k ++] = q[i ++];
    while(j <= r)   tmp[k ++] = q[j ++];
    
    // 復制回原數組
    for(int i = 0, j = l;  j <= r; i++, j++) q[j] = tmp[i];
}

int main() 
{
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &q[i]);
    merge_sort(q, 0, n-1);
    for(int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}

統計逆序對數

#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
int q[N], tmp[N];
int cnt = 0;
int n;

LL merge_sort(int q[], int l, int r){
    if(l >= r) return 0; // 只有一個元素,不必排序,直接返回
    
    int mid = l + r >> 1 ;
    LL res = merge_sort(q, l, mid);
    res += merge_sort(q, mid + 1, r);
    
    // 合並左右兩段
    int i = l, j = mid+1, k = 0; // i = l, j = mid+1
    while(i <= mid && j <= r) {
        if(q[i] <= q[j]) tmp[k ++] = q[i ++];
        else             tmp[k ++] = q[j ++], res += mid - i + 1; // 從i->mid 都大於a[j]
    }
    while(i <= mid) tmp[k ++] = q[i ++], cnt++;
    while(j <= r)   tmp[k ++] = q[j ++];
    
    // 復制回原數組
    for(int i = 0, j = l;  j <= r; i++, j++) q[j] = tmp[i];
    return res;
}

int main() 
{
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &q[i]);
    LL res = merge_sort(q, 0, n-1);
    printf("%lld\n", res);
    return 0;
}

冒泡排序

#include<iostream> //包含輸入輸出頭文件
#include<cmath>
using namespace std; //指定名字空間
int main() 
{ //主函數
	int a[100]; //定義數組,大小100
	int N; //元素的實際個數
	int i = 0, j = 0; //循環變量,並進行初始化
	cin >> N; //輸入元素個數
			  //-------輸入數據-----------
	for (i = 0; i<N; i++) //輸入N個元素
		cin >> a[i]; //循環體只有一行
					 //-------排序---------------
	for (i = 0; i<N - 1; i++) { //控制n-1趟冒泡
		for (j = 0; j<N - 1 - i; j++)
		{
			if (a[j]>a[j + 1]) { //比較相鄰的兩個元素
				int tmp; //臨時變量
				tmp = a[j]; //交換
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}
	}
	//--------輸出----------
	for (i = 0; i<N; i++) 
	{ //使用循環,輸出N個元素
		cout << a[i] << " "; //輸出a[i], 后加空格,不換行
	}
	cout << endl; //所有元素輸出完之后才換行
	return 0; //函數返回
}

整數二分

給定一個按照升序排列的長度為 n 的整數數組,以及 q 個查詢。

對於每個查詢,返回一個元素 k 的起始位置和終止位置(位置從 0 開始計數)。

如果數組中不存在該元素,則返回 -1 -1

輸入格式

第一行包含整數 n 和 q,表示數組長度和詢問個數。

第二行包含 n 個整數(均在 1∼10000 范圍內),表示完整數組。

接下來 q 行,每行包含一個整數 k,表示一個詢問元素。

輸出格式

共 q 行,每行包含兩個整數,表示所求元素的起始位置和終止位置。

如果數組中不存在該元素,則返回 -1 -1

輸入樣例:

6 3
1 2 2 3 3 4
3
4
5

輸出樣例:

3 4
5 5
-1 -1
#include<iostream>
using namespace std;

const int N = 100010;

int a[N];
int n, q, k;

int main()
{
    scanf("%d%d", &n, &q);
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    while(q--){
        scanf("%d", &k);
        
        // 查找左邊界
        int l = 0, r = n - 1;
        
        while(l < r) {
            int mid = l + r >> 1;
            if(a[mid] >= k) r = mid;
            else            l = mid + 1;
        }
        // 如果左邊界不是k,說明數組沒有k,直接輸出-1 -1
        if(a[l] != k) cout<< "-1 -1" << endl;
        else {
            cout << l << " ";
            
            // 查找右邊界
            int l = 0, r = n - 1;
            
            while(l < r) {
                int mid = l + r + 1>> 1;
                if(a[mid] <= k)  l = mid; // 因為這里是l = mid, 上面的mid 要變成  l+r+1 >> 1
                else             r = mid - 1;
            }
            cout << l << endl;
        }
    }
    return 0;
}

浮點數二分

給定一個浮點數 n,求它的三次方根。

輸入格式

共一行,包含一個浮點數 n。

輸出格式

共一行,包含一個浮點數,表示問題的解。

注意,結果保留 6 位小數。

數據范圍

−10000≤n≤10000

輸入樣例:

1000.00

輸出樣例:

10.000000
#include<iostream>

using namespace std;
const double eps = 1e-8;   // eps 表示精度,取決於題目對精度的要求, 一般比所求精度高 2
double get(double a) {
    return a*a*a;
}

int main()
{
    double n, a;
    double l = -10000.0, r = 10000.0;
    double mid;
    scanf("%lf", &n);
    
    while(r - l > eps) {
        mid = (r + l) / 2;
        if(get(mid) >= n) r = mid;
        else              l = mid;
    }
    printf("%.6lf", r);
    return 0;
}

一維前綴和

輸入一個長度為 n 的整數序列。

接下來再輸入 m 個詢問,每個詢問輸入一對 l,r。

對於每個詢問,輸出原序列中從第 l 個數到第 r 個數的和。

#include<iostream>
using namespace std;
const int N = 100010;
int a[N], s[N];
int n, m;

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]), s[i] = s[i-1] + a[i];
    while(m --)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", s[r] - s[l-1]);
    }
    return 0;
}

二維前綴和

輸入一個 n 行 m 列的整數矩陣,再輸入 qq 個詢問,每個詢問包含四個整數 x1,y1,x2,y2,表示一個子矩陣的左上角坐標和右下角坐標。

對於每個詢問輸出子矩陣中所有數的和。

輸入格式

第一行包含三個整數 n,m,q。

接下來 n 行,每行包含 m 個整數,表示整數矩陣。

接下來 q 行,每行包含四個整數 x1,y1,x2,y2,表示一組詢問。

輸出格式

共 q 行,每行輸出一個詢問的結果。

輸入樣例:

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

輸出樣例:

17
27
21
S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)為左上角,(x2, y2)為右下角的子矩陣的和為:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1010;
int s[N][N];
int a[N][N];
int n, m, q;

int main()
{
    scanf("%d%d%d", &n, &m, &q);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            scanf("%d", &a[i][j]);
    
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
    
    while(q --)
    {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]);
    }    
    return 0;
}
image-20220321160704265

一維差分

輸入一個長度為 n 的整數序列。

接下來輸入 m 個操作,每個操作包含三個整數 l,r,c,表示將序列中 [l,r] 之間的每個數加上 c。

請你輸出進行完所有操作后的序列

  • 首先構造差分數組
  • 然后再對區間加減某個數
  • 最后求前綴和得到每個數是多少
#include<iostream>
using namespace std;

const int N = 100010;

int n, m;
int a[N], b[N];
int l, r, c;

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r+1] -= c;
}

int main() 
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    //-----------------------------
    for(int i = 1; i <= n; i++) b[i] = a[i] - a[i-1]; // 構造差分數組!!!
    //-----------------------------
    while(m--) {  //
        scanf("%d%d%d", &l, &r, &c);
        insert(l, r, c);
    }
    //------------------------------
    for(int i = 1; i <= n; i++) b[i] += b[i-1];
    
    for(int i = 1; i <= n; i++) printf("%d ", b[i]);
    
    return 0;
}

二維差分

輸入一個 n 行 m 列的整數矩陣,再輸入 q 個操作,每個操作包含五個整數 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一個子矩陣的左上角坐標和右下角坐標。

每個操作都要將選中的子矩陣中的每個元素的值加上 c。

請你將進行完所有操作后的矩陣輸出。

輸入格式

第一行包含整數 n,m,q。

接下來 n 行,每行包含 m 個整數,表示整數矩陣。

接下來 q 行,每行包含 5 個整數 x1,y1,x2,y2,c,表示一個操作。

輸出格式

共 n 行,每行 m 個整數,表示所有操作進行完畢后的最終矩陣。

輸入樣例:

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

輸出樣例:

2 3 4 1
4 3 4 1
2 2 2 2
給以(x1, y1)為左上角,(x2, y2)為右下角的子矩陣中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c

我們可以先假想a數組為空,那么b數組一開始也為空,但是實際上a數組並不為空,因此我們每次讓以(i,j)為左上角到以(i,j)為右下角面積內元素(其實就是一個小方格的面積)去插入 c=a[i][j],等價於原數組a中(i,j) 到(i,j)范圍內 加上了a[i][j],因此執行n*m次插入操作,就成功構建了差分b數組.

#include <iostream>

using namespace std;

const int N = 1010;

int n, m, q;
int a[N][N], b[N][N];

void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
}

int main()
{
    scanf("%d%d%d", &n, &m, &q);

    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
            scanf("%d", &a[i][j]);
    // 構造差分矩陣
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            insert(i,j,i,j,a[i][j]);
    //
    while(q --)
    {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        insert(x1, y1, x2, y2, c);
    }
    // 求前綴和
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];
    
    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++ ) printf("%d ", b[i][j]);
        puts("");
    }
    
    return 0;
}

雙指針算法

給定一個長度為 nn 的整數序列,請找出最長的不包含重復的數的連續區間,輸出它的長度。

輸入格式

第一行包含整數 nn。

第二行包含 nn 個整數(均在 0∼10^5 范圍內),表示整數序列。

輸出格式

共一行,包含一個整數,表示最長的不包含重復的數的連續區間的長度。

輸入樣例:

5
1 2 2 3 5

輸出樣例:

3
#include<iostream>
using namespace std;

const int N = 100010;
// 第一行包含整數n。
// 第二行包含n個整數(均在0~100000范圍內),表示整數序列。
int n;
int a[N], s[N]; // s[i] 表示 i出現的次數,初始為0 

int main()
{
	int res = 0;
	
	scanf("%d", &n);
	for(int i = 0; i < n; i++) scanf("%d", &a[i]);

	 
	for(int i = 0, j = 0; i < n; i++) {
		
		s[a[i]]++; // a[i]出現次數+1
		
		while(j <= i && s[a[i]] > 1) { // a[i] > 1說明 [j,i]區間出現了重復元素,此時j指針需要向i方向移動,每次移動,就將a[j]在s中個數-1 
									   // j <= i可以去掉,因為上面有s[a[i]]++,到j=i的時候一定有s[a[i]] == 1, 不滿足s[a[i]]>1 會break 
			s[a[j]]--; // a[j]出現次數-1 
			j++;
		} 
		
		res = max(res, i - j + 1);
	} 
	
	cout << res;
	
	return 0;
}

位運算

lowbit(x) = x&(-x)

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

int lowbit(int x) //使用lowbit操作,每次lowbit操作截取一個數字最后一個1后面的所有位
{
    return x & (-x);
}

int main()
{
    int n;
    cin >> n;
    while(n --)
    {
        int x;
        int res = 0;
        cin >> x;
        while(x) x -= lowbit(x), res++; //,每次減去lowbit得到的數字,直到數字減到0,就得到了最終1的個數,
        cout << res << " ";
    }
    return 0;
}

離散化

vector<int> alls; // 存儲所有待離散化的值
sort(alls.begin(), alls.end()); // 將所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重復元素

// 二分求出x對應的離散化的值
int find(int x) // 找到第一個大於等於x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // 映射到1, 2, ...n
}

作者:yxc
鏈接:https://www.acwing.com/blog/content/277/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

區間合並

// 將所有存在交集的區間合並
void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);

    if (st != -2e9) res.push_back({st, ed});

    segs = res;
}

高精度

高精度加法

#include <iostream>
#include <vector>

using namespace std;

vector<int> add(vector<int> &A, vector<int> &B)
{
    if (A.size() < B.size()) return add(B, A);

    vector<int> C;
    int t = 0;
    for (int i = 0; i < A.size(); i ++ )
    {
        t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10);
        t /= 10;
    }

    if (t) C.push_back(t);
    return C;
}

int main()
{
    string a, b;
    vector<int> A, B;
    cin >> a >> b;
    for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');

    auto C = add(A, B);

    for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
    cout << endl;

    return 0;
}

作者:yxc
鏈接:https://www.acwing.com/activity/content/code/content/39792/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

高精度減法

#include <iostream>
#include <vector>

using namespace std;

bool cmp(vector<int> &A, vector<int> &B)
{
    if (A.size() != B.size()) return A.size() > B.size();

    for (int i = A.size() - 1; i >= 0; i -- )
        if (A[i] != B[i])
            return A[i] > B[i];

    return true;
}

vector<int> sub(vector<int> &A, vector<int> &B)
{
    vector<int> C;
    for (int i = 0, t = 0; i < A.size(); i ++ )
    {
        t = A[i] - t;
        if (i < B.size()) t -= B[i];
        C.push_back((t + 10) % 10);
        if (t < 0) t = 1;
        else t = 0;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

int main()
{
    string a, b;
    vector<int> A, B;
    cin >> a >> b;
    for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
    for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');

    vector<int> C;

    if (cmp(A, B)) C = sub(A, B);
    else C = sub(B, A), cout << '-';

    for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
    cout << endl;

    return 0;
}

作者:yxc
鏈接:https://www.acwing.com/activity/content/code/content/39793/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

高精度乘低精度

#include <iostream>
#include <vector>

using namespace std;


vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;

    int t = 0;
    for (int i = 0; i < A.size() || t; i ++ )
    {
        if (i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }

    while (C.size() > 1 && C.back() == 0) C.pop_back();

    return C;
}


int main()
{
    string a;
    int b;

    cin >> a >> b;

    vector<int> A;
    for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');

    auto C = mul(A, b);

    for (int i = C.size() - 1; i >= 0; i -- ) printf("%d", C[i]);

    return 0;
}

作者:yxc
鏈接:https://www.acwing.com/activity/content/code/content/39794/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

高精度除以低精度

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

using namespace std;

vector<int> div(vector<int> &A, int b, int &r)
{
    vector<int> C;
    r = 0;
    for (int i = A.size() - 1; i >= 0; i -- )
    {
        r = r * 10 + A[i];
        C.push_back(r / b);
        r %= b;
    }
    reverse(C.begin(), C.end());
    while (C.size() > 1 && C.back() == 0) C.pop_back();
    return C;
}

int main()
{
    string a;
    vector<int> A;

    int B;
    cin >> a >> B;
    for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');

    int r;
    auto C = div(A, B, r);

    for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];

    cout << endl << r << endl;

    return 0;
}

作者:yxc
鏈接:https://www.acwing.com/activity/content/code/content/39795/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

高精度階乘

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

const int N = 1010;
// 存儲1-1000的階乘
vector<int> F[N];

// 高精度乘法
vector<int> mul(vector<int> &A, int b)
{
    int t =0;
    vector<int> C;
    for(int i=0;i<A.size() || t;i++)
    {
        if(i < A.size()) t += A[i] * b;
        C.push_back(t % 10);
        t /= 10;
    }
    A = C; // 更新A,沒有這個會得不到正確答案
    return C;
}



int main()
{
    int a;
    F[0] = {1};
    vector<int> A{1};
    // 計算階層並存儲
    for(int i=1;i<=1000;i++)
    {
        F[i] = mul(A,i);
    }
    while(cin >> a)
    {
        // 查表輸出
        for(int i=F[a].size()-1;i>=0;i--) cout << F[a][i];
        cout << endl;
    }
}

鏈接:https://www.acwing.com/solution/content/89358/
來源:AcWing

高精度進制轉換

將 M 進制的數 X 轉換為 N 進制的數輸出。

輸入格式

第一行包括兩個整數:M 和 N。

第二行包含一個數 X,X 是 M 進制的數,現在要求你將 M 進制的數 X 轉換成 N 進制的數輸出。

輸出格式

共一行,輸出 X 的 N 進制表示。

數據范圍

2≤N,M≤36,
X 最多包含 100位。
在輸入中,當某一位數字的值大於 10(十進制下)時,我們用大寫字母 A∼Z,分別表示(十進制下的)數值 10∼35。
在輸出中,當某一位數字的值大於 10(十進制下)時,我們用小寫字母 a∼z,分別表示(十進制下的)數值 10∼35。

輸入樣例:

10 2
11

輸出樣例:

1011
#include <bits/stdc++.h>

using namespace std;


string conversion(string s, int m, int n)
{
	vector<int> a;
    for (int i = s.size() - 1; i >= 0; i--)
    {
        char c = s[i];
        if (c >= 'A') a.push_back(c - 'A' + 10);
        else a.push_back(c - '0');
    }
    string res;
    if (s == "0") res = "0";
    else {
        while (a.size())
        {
            int r = 0;
            for (int i = a.size() - 1; i >= 0; i--)
            {
                a[i] += r * m;
                r = a[i] % n;
                a[i] /= n;
            }
            while (a.size() && a.back() == 0) a.pop_back();
            if (r < 10) res += to_string(r);
            else res += r - 10 + 'a';
        }
        reverse(res.begin(), res.end()); // 這一步重要
    }
    return res;
}

int main()
{
	int m, n;
	string s;
	cin >> m >> n >> s;
	cout << conversion(s, m, n) << endl;
	return 0;
}

Java 大整數

import java.util.Scanner;
import java.math.BigInteger;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        BigInteger a,b;
            a=input.nextBigInteger();
            b=input.nextBigInteger();
            System.out.println(a.add(b));
        }
    }

import java.util.*;
import java.math.BigInteger;
public class Main {
    public static void main(String args[]) {
        Scanner input = new Scanner(System.in);
        BigInteger num1 = input.nextBigInteger();
        BigInteger num2 = input.nextBigInteger();
        System.out.println(num1.subtract(num2));
    }
}

import java.util.* ;
import java.math.BigInteger ;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in) ;
        BigInteger m,n;
        m=input.nextBigInteger();
        n=input.nextBigInteger();
            m = m.multiply(n) ;
        System.out.println(m);
    }
}

import java.util.*;
import java.math.BigInteger;
public class Main {
    public static void main(String args[]) {
        Scanner input = new Scanner(System.in);
        BigInteger num1 = input.nextBigInteger();
        BigInteger num2 = input.nextBigInteger();
        System.out.println(num1.divide(num2));//相除
        System.out.println(num1.remainder(num2));//余數
    }
}

64位整數乘

求 a 乘 b 對 p 取模的值。

輸入格式

第一行輸入整數a,第二行輸入整數b,第三行輸入整數p。

輸出格式

輸出一個整數,表示a*b mod p的值。

數據范圍

1≤a,b,p≤10^18

輸入樣例:

3
4
5

輸出樣例:

2

a*10 = a*(1011)

和快速冪的思想類似,只不過把里面的乘換成了加

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long LL;

LL qadd(LL a, LL b, LL p)
{
	LL res = 0;
	LL t = a;
	while(b)
	{
		if(b & 1) res = (res + t) % p;
		t = (t + t) % p;
		b >>= 1;
	}
	return res;
 } 
 
int main()
{
	LL a, b, p;
	scanf("%lld%lld%lld", &a, &b, &p);
	printf("%lld\n", qadd(a, b, p));
	return 0;
}

數據結構

單鏈表

const int N = 100010, M = N * 2;
int h[N], e[M], w[M], ne[M], idx = 0;
void init()
{
    memset(h, -1, sizeof h);
}
void add(int a, int b, int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}

並查集|帶size

  1. 給定一個包含 n 個點(編號為 1∼n)的無向圖,初始時圖中沒有邊。

    現在要進行 m 個操作,操作共有三種:

    1. C a b,在點 a 和點 b 之間連一條邊,a 和 b 可能相等;
    2. Q1 a b,詢問點 a 和點 b 是否在同一個連通塊中,a 和 b 可能相等;
    3. Q2 a,詢問點 a 所在連通塊中點的數量
#include<iostream>
#include<cstdio>
using namespace std;

const int N = 100010;

int p[N], cnt[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) p[i] = i, cnt[i] = 1;

    while (m -- )
    {
        string op;
        cin >> op;
        if(op == "C")
        {
            int a, b;
            cin >> a >> b;
            int fa = find(a), fb = find(b);
            if(fa != fb)
            {
                p[fa] = fb;
                cnt[fb] += cnt[fa];
            }
        }
        else if(op == "Q1")
        {
            int a, b;
            cin >> a >> b;
            int fa = find(a), fb = find(b);
            if(fa == fb) cout << "Yes" << endl;
            else         cout << "No" << endl;
        }
        else
        {
            int a;
            cin >> a;
            cout << cnt[find(a)] << endl;
        }
    }

    return 0;
}

Trie樹

維護一個字符串集合,支持兩種操作:

“I x”向集合中插入一個字符串x;
“Q x”詢問一個字符串在集合中出現了多少次。
共有N個操作,輸入的字符串總長度不超過 10^5,字符串僅包含小寫英文字母。

輸入格式

//trie樹算法
#include<iostream>
#include<cstdio>
#include<cstring>
const int N = 1000010;

using namespace std;
int son[N][26], cnt[N], idx;
// son[][]存儲樹中每個節點的子節點,
// cnt[i]表示該單詞i的出現次數
// idx表現當前用到了哪個節點,idx單調遞增

void insert(char * str)
{
	int p = 0; // parent
	for(int i = 0; str[i]; i++)
	{
		int u = str[i] - 'a';
		if(!son[p][u]) son[p][u] = ++idx; // idx = 0 是根節點root 
		p = son[p][u]; // 繼續該單詞的下一個節點 
	 } 
	 cnt[p]++;
}

int query(char * str)
{
	int p = 0;
	for(int i = 0; str[i]; i++)
	{
		int u = str[i] - 'a';
		if(!son[p][u]) return 0;
		p = son[p][u];
	}
	return cnt[p];
}
 
int main()
{
	int n;
	scanf("%d", &n);
	char op[2];
	char str[N];
	while(n--)
	{
		scanf("%s%s",op,str);
		if(op[0] == 'I') insert(str);
		else 			 printf("%d\n", query(str));
	}	
	return 0;
} 

KMP

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 1010; //N為模式串長度,M匹配串長度
int n, m, q;
// char s[N], p[M];  //s為模式串, p為匹配串
char s[N], t[N]; 
// 都存到 s+1, p+1里面去 
// 模式串p在源串s中出現的位置,m是s的長度,n是q長度 
int KMP(char s[], char q[], int m, int n)
{
	int res = 0;
	
	int ne[N]; //next[]數組,避免和頭文件next沖突
	
	for(int i=2,j=0;i<=n;i++)
    //j表示匹配成功的長度,i表示q數組中的下標,因為q數組的下標是從1開始的,只有1個時,一定為0,所以i從2開始
    {
        while(j&&q[i]!=q[j+1]) j=ne[j];
        //如果不行可以換到next數組
        if(q[i]==q[j+1]) j++;
        //成功了就加1
        ne[i]=j;
        //對應其下標
    }
    //j表示匹配成功的長度,因為剛開始還未開始匹配,所以長度為0
    for(int i=1,j=0;i<=m;i++)
    {
        while(j&&s[i]!=q[j+1]) j=ne[j];
        //如果匹配不成功,則換到j對應的next數組中的值
        if(s[i]==q[j+1]) j++;
        //匹配成功了,那么j就加1,繼續后面的匹配
        // 匹配成功!!
        if(j==n)//如果長度等於n了,說明已經完全匹配上去了
        {
        	res++; 
            //printf("%d ",i-j);/ 
            //因為題目中的下標從0開始,所以i-j不用+1;
            j=ne[j];
            //為了觀察其后續是否還能跟S數組后面的數配對成功
        }
    }
    return res;
}
int main()
{
	cin >> n >> m >> q;
	
	cin >> s+1 >> t+1;
	
	while(q --)
	{
		int l, r;
		scanf("%d %d", &l, &r);
		char ts[N];
		for(int i = l, j = 1; i <= r; i++, j++) ts[j] = s[i];
		int res = KMP(ts, t, r-l+1, m); // 模式串s, 模板串q, s長度,q長度 
		printf("%d\n", res);
	}
	return 0;
} 

存圖

鄰接矩陣


鄰接表vector

const int N = 100010;
int n, m;
vector<int> G[N]; // 存圖
// 在頂點a和b之間建立一條邊
// 頭插法
void add(int a, int b)
{
   G[a].push_back(b);
}

int main()
{
    // 遍歷一個點能到的其他點
    // a點能到的其他點
    for(auto p : G[a])
    {
        int t = p;
        // t就是與a鄰接的點
    }
}

帶權:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
#define N 10000
using namespace std;
struct EDGE
{
    int to;//終點
    int cost;//邊的權值
};
vector<EDGE>G[N];//G[i]中i表示出發點

void add(int a, int b, int w)
{
    EDGE e;
    e.to = b;
    e.cost = w;
    G[a].push_back(e);
}

int m,n;
int temp1;//出發點
int main()
{
    scanf("%d%d",&n,&m);
    while(m--)
    {
        EDGE e;
        scanf("%d%d%d",&temp1,&e.to,&e.cost);//輸入出發點,終點,邊的權值
        G[temp1].push_back(e);//將數據壓入動態數組,表示在這個出發點下引出的邊
        //相當於二維動態數組
    }
    for (int i=1; i<=n; i++) //按照出發點的順序遍歷
    {
        for(int j=0; j<G[i].size(); j++) //遍歷出發點所引出的邊
        {
            EDGE e=G[i][j];//1以二維數組形式輸出
            printf("from %d to %d,the cost is %d\n",i,e.to,e.cost);
        }
    }
    return 0;
}

數組模擬鏈表

const int N = 100010;
int h[N], e[N], ne[N], idx = 0; // n個單鏈表,用來存每個點能夠到達的其他頂點

void init()
{
	memset(h, -1, sizeof h); // 初始所有表頭指向-1
}
// 在頂點a和b之間建立一條邊
// 頭插法
void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}


int main()
{
    // 遍歷一個點能到的其他點
    // a點能到的其他點
    for(int i = h[a]; i != -1; i = ne[i])
    {
        int t = e[i];
        // t就是與a鄰接的點
    }
}

帶權

const int N = 2010, M = 10010;

int h[N], w[M], e[M], ne[M], idx;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int main()
{
     for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
         	int cost = w[i];
        }
}

搜索

dfs

給定一個整數 n,將數字 1∼n 排成一排,將會有很多種排列方法。

現在,請你按照字典序將所有的排列方法輸出。

  • 使用path[]保存每層選擇哪個數
  • 使用st[]標記每個數是不是被上面的某層使用過
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 10; //

int path[N]; // 用來存每層選擇的數是哪個
bool st[N]; // 用來表示一個數是否被用過 

int n;

// 當前是第u層 ,從第0層開始 
void dfs(int u)
{
	if(u == n) // 到達第n層,可以輸出並返回了 
	{
		for(int i = 0; i < n; i++ )
		{
			printf("%d ", path[i]);
		 } 
		 puts("");
		 return;
	} 
	
	for(int i = 1; i <= n; i++)
	{
		if(!st[i]) // 這個數沒用過 
		{
			path[u] = i; // 本層用i這個數
			st[i] = true; 
			dfs(u + 1); // 遞歸到下一層 
			st[i] = false; 	  // 恢復現場
		}	
	}
} 

int main()
{
	scanf("%d", &n);
	dfs(0);
	return 0;
 } 

dfs剪枝+順序

小貓爬山

翰翰和達達飼養了 N 只小貓,這天,小貓們要去爬山。

經歷了千辛萬苦,小貓們終於爬上了山頂,但是疲倦的它們再也不想徒步走下山了(嗚咕>_<)。

翰翰和達達只好花錢讓它們坐索道下山。

索道上的纜車最大承重量為 W,而 NN 只小貓的重量分別是 C1、C2……CN。

當然,每輛纜車上的小貓的重量之和不能超過 W。

每租用一輛纜車,翰翰和達達就要付 1 美元,所以他們想知道,最少需要付多少美元才能把這 N 只小貓都運送下山?

輸入格式

第 1 行:包含兩個用空格隔開的整數,N 和 W。

第 2..N+1 行:每行一個整數,其中第 i+1 行的整數表示第 ii 只小貓的重量 Ci。

輸出格式

輸出一個整數,表示最少需要多少美元,也就是最少需要多少輛纜車。

數據范圍

1≤N≤181≤N≤18,
1≤Ci≤W≤1081≤Ci≤W≤108

輸入樣例:

5 1996
1
2
1994
12
29

輸出樣例:

2
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 30;
int n, Wmax;
int ans = 0x3f3f3f3f; // 記錄最終結果 
int sum[N]; // sum[i]表示第i輛扯已經裝載了多少重量
int w[N]; // 每個小貓的重量 
// 當前考慮第u只小貓,已經使用了k輛車 
void dfs(int u, int k)
{
	if(k >= ans) return; // 重要剪枝!!如果當前搜索到的答案已經比最小的大了,后面不用搜索了
	
	if(u >= n) // 已經裝完了所有n只小貓 
	{
		ans = min(ans, k);
		return;
	}
	
	for(int i = 0; i < k; i++)
	{
		// 第i輛車可以裝下小貓u 
		if(sum[i] + w[u] <= Wmax)
		{
			sum[i] += w[u];
			dfs(u+1, k);
			sum[i] -= w[u]; // 恢復現場 
		}
	}
	// 新開一輛車
	sum[k] += w[u]; 
	dfs(u+1, k+1);
	sum[k] -= w[u]; 
 } 
 
int main()
{
	scanf("%d%d", &n, &Wmax);
	for(int i = 0; i < n; i++) scanf("%d", &w[i]);
	
	sort(w, w+n);
	reverse(w, w+n); // 從大到小排序,剪枝【可有可無】
	
	dfs(0, 0); // 從第0只小貓開始,使用0輛車 
	
	printf("%d", ans);
	return 0;
}

題目描述

給定 n 個正整數,將它們分組,使得每組中任意兩個數互質。

至少要分成多少個組?

輸入格式

第一行是一個正整數 n。

第二行是 n 個不大於10000的正整數。

輸出格式

一個正整數,即最少需要的組數。

數據范圍

1≤n≤10

輸入樣例:

6
14 20 33 117 143 175

輸出樣例:

3
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}
bool isZ(int a, int b)
{
    if(gcd(a, b) > 1) return false;
    else              return true;
}
int ans = 0x3f3f3f3f;
vector<int> h[12];
int a[12];
int n;

void dfs(int u, int k)
{
    if(k >= ans) return; // 剪枝

    if(u >= n)
    {
        ans = min(ans, k);
        return;
    }

    for(int i = 0; i < k; i++)
    {
        vector<int> tmp = h[i]; // 第k組有的所有互質的數
        bool flag = true;
        for(int j = 0; j < tmp.size(); j++)
        {
            if(!isZ(a[u], tmp[j]))
            {
                flag = false;
                break;
            }
        }

        if(flag)
        {
            h[i].push_back(a[u]);
            dfs(u+1, k);
            h[i].pop_back(); // 恢復現場
        }
    }

    h[k].push_back(a[u]);
    dfs(u+1, k+1);
    h[k].pop_back();
}

int main()
{
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];

    dfs(0, 0);

    cout << ans;
    return 0;
}

記憶化dfs

給定一個 R 行 C 列的矩陣,表示一個矩形網格滑雪場。

矩陣中第 i 行第 j 列的點表示滑雪場的第 i 行第 j 列區域的高度。

一個人從滑雪場中的某個區域內出發,每次可以向上下左右任意一個方向滑動一個單位距離。

當然,一個人能夠滑動到某相鄰區域的前提是該區域的高度低於自己目前所在區域的高度。

下面給出一個矩陣作為例子:

1  2  3  4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

在給定矩陣中,一條可行的滑行軌跡為 24−17−2−1

在給定矩陣中,最長的滑行軌跡為 25−24−23−…−3−2−1,沿途共經過 25 個區域。

現在給定你一個二維矩陣表示滑雪場各區域的高度,請你找出在該滑雪場中能夠完成的最長滑雪軌跡,並輸出其長度(可經過最大區域數)。

輸入格式

第一行包含兩個整數 R 和 C。

接下來 R 行,每行包含 C 個整數,表示完整的二維矩陣。

輸出格式

輸出一個整數,表示可完成的最長滑雪長度。

數據范圍

1≤R,C≤300
0≤矩陣中整數≤10000

輸入樣例:

5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

輸出樣例:

25
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring> 
using namespace std;
const int N = 310;
int n, m;
int f[N][N];
int g[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int dfs(int x, int y)
{
	int &v = f[x][y];
	if(v != -1) return v;
	
	// 遍歷四個方向
	v = 1; // 這個很重要!!! 
	for(int i = 0; i < 4; i++)
	{
		int nx = x + dx[i], ny = y + dy[i];
		
		if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && g[x][y] > g[nx][ny])
		{
			v = max(v, dfs(nx, ny) + 1);
		}
		
	 } 
	
	return v;
 } 
 
 
 int main()
 {
 	scanf("%d%d", &n, &m);
 	for(int i = 1; i <= n; i++)
 		for(int j = 1; j <= m; j++)
 			scanf("%d", &g[i][j]);
 			
 			
 	memset(f, -1, sizeof f);
 	int res = 0;
 	for(int i = 1; i <= n; i++)
	 	for(int j = 1; j <= m; j++)
		{
		 	res = max(res, dfs(i, j));
//			printf("%d ", dfs(i, j));
		}
	cout << res << endl;
	return 0; 
 }

bfs

給定一個 n×m 的二維整數數組,用來表示一個迷宮,數組中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通過的牆壁。

最初,有一個人位於左上角 (1,1) 處,已知該人每次可以向上、下、左、右任意一個方向移動一個位置。

請問,該人從左上角移動至右下角 (n,m) 處,至少需要移動多少次。

數據保證 (1,1) 處和 (n,m) 處的數字為 0,且一定至少存在一條通路。

輸入格式

第一行包含兩個整數 n 和 m。

接下來 n 行,每行包含 m 個整數(0 或 1),表示完整的二維數組迷宮。

輸出格式

輸出一個整數,表示從左上角移動至右下角的最少移動次數。

數據范圍

1≤n,m≤100

輸入樣例:

5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

輸出樣例:

8
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring> // memset
using namespace std;

typedef pair<int , int> PII;
const int N = 110, INF = 0x3f3f3f3f;

int g[N][N];
int d[N][N];
// bool st[N][N];
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1}; // 上下左右四個方向 

int n, m;

int bfs()
{
	queue<PII> q;
	q.push({0 ,0});
	
	
	memset(d, 0x3f, sizeof d);// 每個點初始距離為0x3f3f3f3f 更好
	
	d[0][0] = 0; //到起點的距離初始化為0
// 	st[0][0] = true;
	while(!q.empty())
	{
		PII t = q.front();
		q.pop();
		int x = t.first, y = t.second; 
		
		for(int i = 0; i < 4; i++)
		{
			int nx = x + dx[i], ny = y + dy[i];
            // 第一次發現該點並且該點可以走
			if(nx >= 0 && nx < n && ny >= 0 && ny < m && d[nx][ny] == INF && g[nx][ny] == 0)
			{
				// st[nx][ny] = true;
				q.push({nx, ny});
				d[nx][ny] = d[x][y] + 1;
			}
		}
	 } 
	return d[n-1][m-1];	
} 

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 0; i < n; i++)
		for(int j = 0; j < m; j++)
			scanf("%d", &g[i][j]);
	cout << bfs() << endl;
	
	return 0;
}

8數碼

在一個 3×3 的網格中,1∼8 這 8 個數字和一個 x 恰好不重不漏地分布在這 3×3 的網格中。

例如:

1 2 3
x 4 6
7 5 8

在游戲過程中,可以把 x 與其上、下、左、右四個方向之一的數字交換(如果存在)。

我們的目的是通過交換,使得網格變為如下排列(稱為正確排列):

1 2 3
4 5 6
7 8 x

例如,示例中圖形就可以通過讓 x 先后與右、下、右三個方向的數字交換成功得到正確排列。

交換過程如下:

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x

現在,給你一個初始網格,請你求出得到正確排列至少需要進行多少次交換。

輸入格式

輸入占一行,將 3×3 的初始網格描繪出來。

例如,如果初始網格如下所示:

1 2 3 
x 4 6 
7 5 8 

則輸入為:1 2 3 x 4 6 7 5 8

輸出格式

輸出占一行,包含一個整數,表示最少交換次數。

如果不存在解決方案,則輸出 −1。

輸入樣例:

2  3  4  1  5  x  7  6  8

輸出樣例

19

思路就是把每種狀態看成一個節點,初始的狀態距離為0,每切換一個狀態,到下一個狀態的距離就是當前狀態距離+1,求到最后一個狀態節點的距離

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<unordered_map>
#include<queue> 
#include<cstring> 
using namespace std; 



int bfs(string start)
{
	string end = "12345678x"; // 結束狀態 
	
	unordered_map<string, int> d; // 用來存到某個狀態的距離 
	d[start] = 0;
	queue<string> q;
	
	q.push(start);
	
	// 對每一個狀態,進行bfs搜索 
	while(q.size()) 
	{
		string t = q.front();
		q.pop();
		int distance = d[t]; 
		
		if(t == end) return distance; //找到了最終狀態,bfs第一次發現的就是最短的路徑 
		
		int k = t.find('x');
		int ax = k / 3, ay = k % 3; // 得到x在start轉成三維矩陣后的坐標
		// 上下左右四個方向
		int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
		for(int i = 0; i < 4; i++)
		{
			int nx = ax + dx[i], ny = ay + dy[i];
			if(nx >= 0 && nx < 3 && ny >= 0 && ny < 3)
			{
				swap(t[k], t[nx * 3 + ny]);
				if(!d.count(t))  //如果這個狀態沒有出現過 
				{
					d[t] = distance + 1;
					q.push(t);
				} 
				swap(t[k], t[nx * 3 + ny]);
			}
		} 
	}
	
	return -1;	 
}

int main()
{
	string s, start; 
	for(int i = 0; i < 9; i++)
	{
		cin >> s;
		start += s;
	}
	
	cout << bfs(start) << endl;
	return 0;
}


圖論

拓撲排序

給定一個 n 個點 m 條邊的有向圖,點的編號是 1 到 n,圖中可能存在重邊和自環。

請輸出任意一個該有向圖的拓撲序列,如果拓撲序列不存在,則輸出 −1。

若一個由圖中所有點構成的序列 A 滿足:對於圖中的每條邊 (x,y),x 在 A 中都出現在 y 之前,則稱 A 是該圖的一個拓撲序列。

d[u]記錄每個節點u的入度

維護一個隊列q,把所有入度為0的點放入隊列中

每次從隊列中取一個點,遍歷該點所有的鄰接點,並將每個鄰接點的入度-1,如果-1后該鄰接點的入度也變成了0,那么將其加入隊列

同時使用res[N]記錄每個進入過隊列中的點

如果隊列為空時,res的大小為n說明存在拓撲排序,輸出即可;否則說明存在環

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;

const int N = 100010, M = 2*N;

int n, m; 
int d[N];// 存每個節點的入度 
queue<int> q; //存所有入度為0的點 
int res[N], cnt = 0;
//存圖
int h[N], e[M], ne[M], idx = 0;
void add(int a, int b)
{
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

void tor()
{
	// 先把入度為0的點加入隊中 
	for(int i = 1; i <= n; i++)
		if(!d[i])
		{
			q.push(i);
			res[cnt++] = i;		
		}
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		// t所有指向的點 
		for(int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			
			d[j]--; // 該點入度-1 
			if(!d[j]) 
			{
				q.push(j); // 若度為0,加入隊列 
				res[cnt++] = j;	
			}
		}
	}
}

int main()
{
	memset(h, -1, sizeof h);
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
		d[b] ++;
	}
	
	tor();
	
	if(cnt < n) cout << -1 << endl;
	else {
		for(int i = 0; i < n; i++)
		cout << res[i] << " ";
	}
	puts("");
	
	return 0;
}

Dijkstra 最短路

給定一個 n 個點 m 條邊的有向圖,圖中可能存在重邊和自環,所有邊權均為正值。

請你求出 1 號點到 n 號點的最短距離,如果無法從 1 號點走到n 號點,則輸出 −1。

注意不能存在負邊!

模板

注意初始化的時候,初始化為正無窮

循環n-1次,然后每次確定一個點的最短距離

朴素做法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 510;
int g[N][N]; // 用鄰接矩陣存圖
int st[N];// st[i] = true 表示i點的最短距離已經確定了
int dist[N]; // 存從1到每個點的最短距離
int n, m;
// 返回從1到n的最短距離 
int dijkstra()
{
	// 初始化從1到所有點距離為正無窮 
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;

	
	// 循環n-1次每次取未確定的點里面距離最小的 
	for(int i = 0; i < n-1; i++)
	{
		// 從沒確定的點里面選一個最小的值 t 
		int t = -1;
		for(int i = 1; i <= n; i++) 
			if(!st[i] && (t == -1 || dist[i] < dist[t]))
				t = i;
		
		// 跟新t指向節點的最短距離 
		for(int i = 1; i <= n; i++)
			// dist[i] = min(dist[i], dist[t] + g[t][i]);
			if(!st[i] && dist[i] > dist[t] + g[t][i])
				dist[i] = dist[t] + g[t][i]; 
		
		
		st[t] = true; //確定了一個點的最短距離 
	}
	
	if(dist[n] == INF)        return -1;
	else					  return dist[n];
} 

int main()
{
	// 初始化所有點之間邊權為無窮大 
	memset(g, 0x3f, sizeof g);
	 
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		g[a][b] = min(g[a][b], c); // 有重邊的話選小的那個 
	}
	int t = dijkstra();
	
	printf("%d\n", t); 
	return 0;
}

堆優化做法

適用范圍:所有邊權為正

代碼1:用vector建圖

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring> 
using namespace std;
typedef pair<int, int> PII;
const int N = 150010;
const int INF = 0x3f3f3f3f;

int dist[N]; // 從1到每個點的最短距離 
bool st[N]; // 每個點是否出現過 
int n, m;

// 一個點和邊權
struct VER
{
	int to;
	int w;	
}; 
vector<VER> h[N];
// a->b 邊權是w 
void add(int a, int b, int w)
{
	VER ver;
	ver.to = b;
	ver.w = w;
	h[a].push_back(ver); 
} 
// 定義圖 


int dijkstra()
{
	// 初始化所有點的最小距離為INF,1的最小距離為0 
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	
	priority_queue< PII, vector<PII>, greater<PII> > heap; //定義小根堆 
	heap.push({0,1}); // {distance, ver} push到堆里的是一個pair,第一個是到該點的最短距離,第二個是該點的編號,
				       // 因為優先隊列按照第一個排序 
	// 每次不continue的時候確定一個點的最小距離 
	while(heap.size())
	{
		PII t = heap.top();
		heap.pop();
		int distance = t.first, ver = t.second;
		
		if(st[ver]) continue; // 因為一個點的距離會被放入堆多次,只需要取一次最小的就行
		
		 
		// 遍歷ver指向的所有點j 
		for(int i = 0; i < h[ver].size(); i++)
		{
			int j = h[ver][i].to;
			int w = h[ver][i].w;
			// 如果j沒被確定最小距離,並且可以更新的話
			// 就更新j的最短距離,同時加入堆	
			if(dist[j] > distance + w)
			{
				dist[j] = distance + w;
				heap.push({dist[j], j}); //dist[j]會被放入多次,會有冗余,只需要取最小的那個就行 
			}
		}
		st[ver] = true; // 確定了該點的最小距離 
	}
	if(dist[n] == INF) return -1;
	else 			   return dist[n];
}

int main()
{
	scanf("%d%d", &n, &m);
	while(m --)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		// 重邊冗余存儲,不會對dijkstra有影響 
		add(a, b, w); 
	}
	int t = dijkstra();
	printf("%d\n", t);
	return 0; 
}

代碼2:用鏈表建圖

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 1e6 + 10;

int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    cout << dijkstra() << endl;

    return 0;
}

作者:yxc
鏈接:https://www.acwing.com/activity/content/code/content/48493/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

Bellma_ford

給定一個 n 個點 m 條邊的有向圖,圖中可能存在重邊和自環, 邊權可能為負數

請你求出從 1 號點到 n 號點的最多經過 k 條邊的最短距離,如果無法從 1 號點走到 n 號點,輸出 impossible

注意:圖中可能 存在負權回路

輸入格式

第一行包含三個整數 n,m,k

接下來 m 行,每行包含三個整數 x,y,z表示存在一條從點 x 到點 y 的有向邊,邊長為 z。

輸出格式

輸出一個整數,表示從 1 號點到 n 號點的最多經過 k 條邊的最短距離。

如果不存在滿足條件的路徑,則輸出 impossible

數據范圍

1≤n,k≤500
1≤m≤10000
任意邊長的絕對值不超過 10000

輸入樣例:

3 3 1
1 2 1
2 3 1
1 3 3

輸出樣例:

3
  1. 初始化操作和dijkstra相同:1的最短距離初始化為0,其他點初試化為INF
  2. 因為限制了要走的步數,蓑衣用bellman_ford算法:迭代k次可以找到從1出發走k步到個點的最短距離
  3. 注意每輪迭代進行松弛操作的時候,更新最短距離使用的是上次的結果,防止更新每條邊的時候有串聯效應
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510;
const int M = 10010;
const int INF = 0x3f3f3f3f;
struct Edge
{
	int a, b, w; // a->b 的邊權為w 
}edges[M];// 保存所有邊

int n, m, k;
int dist[N]; //當前 從1到每個點的最短路 
int last[N]; // 上一次迭代從1到每個點的最短路

void bellman_ford()
{
	// 和dijkstra一樣初始化
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	// 迭代k次 ,就是松弛k輪 
	for(int i = 0; i < k; i++) 
	{
		memcpy(last, dist, sizeof dist); // 保存上一次迭代的結果
		for(int j = 0; j < m; j++) // 對所有邊進行松弛操作 
		{
			int a = edges[j].a, b = edges[j].b, w = edges[j].w;
			dist[b] = min(dist[b], last[a] + w);
		}			
	} 
	
	
}

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 0; i < m; i++)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		edges[i] = {a, b, w}; // edges[i].a = a;  edges[i].b = b;  edges[i].w = w; 
	}
	bellman_ford();
	
	if(dist[n] > INF / 2) printf("impossible"); //這里是因為存在負權邊,有可能到不了n但是存在負邊使得dist[n] < INF 
	else				  printf("%d\n", dist[n]);
	return 0; 
 } 

Spfa 判斷負環

給定一個n 個點 m 條邊的有向圖,圖中可能存在重邊和自環, 邊權可能為負數

請你判斷圖中是否存在負權回路。

輸入格式

第一行包含整數 n 和 m。

接下來 m 行每行包含三個整數 x,y,z,表示存在一條從點 x 到點 y 的有向邊,邊長為 z。

輸出格式

如果圖中存在負權回路,則輸出 Yes,否則輸出 No

數據范圍

1≤n≤2000
1≤m≤10000
圖中涉及邊長絕對值均不超過 10000

輸入樣例:

3 3
1 2 -1
2 3 4
3 1 -4

輸出樣例:

Yes

spfa就是隊列優化的bellman_ford算法

使用spfa判斷圖中是否存在負環的話,有兩種方法

  1. 判斷一個點是不是已經進入隊列了n次,由bellman_ford算法可以知道,如果不存在負環最多經過n次迭代就可以得到1到任何一個點的最短距離,一個點最多被更新n-1次
  2. 判斷到當前點的最短路徑長度是不是大於等於n了!如果是的話,就說明存在一條路徑有n的長度,那么該路徑有n+1個點,必有兩個點相同,所以一定存在負環

需要注意的是判斷整個圖存不存在負環,並不是判斷從1開始存不存在負環,所以一開始要把所有點加入到隊列中

代碼1:用數組模擬單鏈表建圖

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 2010, M = 10010;

int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

bool spfa()
{
    queue<int> q;

    for (int i = 1; i <= n; i ++ )
    {
        st[i] = true;
        q.push(i);
    }

    while (q.size())
    {
        int t = q.front();
        q.pop();

        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;

                if (cnt[j] >= n) return true;
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);

    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    if (spfa()) puts("Yes");
    else puts("No");

    return 0;
}

作者:yxc
鏈接:https://www.acwing.com/activity/content/code/content/48499/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

代碼2:用vector建圖

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector> 
#include<queue>
using namespace std;
const int N = 2010;
const int INF = 0x3f3f3f3f;
int n, m;
bool st[N];
int dist[N];
int cnt[N];
struct VER
{
	int to;
	int w;
};
vector<VER> h[N];

void add(int a, int b, int w)
{
	VER ver;
	ver.to = b;
	ver.w  = w;
	h[a].push_back(ver);
}

bool spfa()
{
// 	memset(dist, 0x3f, sizeof dist);
// 	dist[1] = 0;
	
	queue<int> q;
	// 所有點都入隊 
	for(int i = 1; i <= n; i++)
	{
		st[i] = true;
		q.push(i);
	}
	
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		st[t] = false;
		if(cnt[t] >= n) return true;
		
		for(int i = 0; i < h[t].size(); i++)
		{
			int j = h[t][i].to, w = h[t][i].w;
			
			if(dist[j] > dist[t] + w)
			{
				dist[j] = dist[t] + w;
				cnt[j] = cnt[t] + 1; // t->j路徑長度+1 
				
				
				if(!st[j]) // j不在隊列中 
				{
					q.push(j);
					st[j] = true;
					
				}	
			}	
		} 
	}
	
	return false;
}	

int main()
{
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		add(a, b, w);
	}
	bool t = spfa();
	
	if(t) puts("Yes");
	else  puts("No");
	
	return 0;
}

Floyd 最短路

給定一個 n 個點 m 條邊的有向圖,圖中可能存在重邊和自環,邊權可能為負數

再給定 k 個詢問,每個詢問包含兩個整數 x 和 y,表示查詢從點 x 到點 y 的最短距離,如果路徑不存在,則輸出 impossible

數據保證圖中不存在負權回路。

輸入格式

第一行包含三個整數 n,m,k

接下來 m 行,每行包含三個整數 x,y,z,表示存在一條從點 x 到點 y 的有向邊,邊長為 z。

接下來 k 行,每行包含兩個整數 x,y表示詢問點 x 到點 y 的最短距離。

輸出格式

共 k 行,每行輸出一個整數,表示詢問的結果,若詢問兩點間不存在路徑,則輸出 impossible

數據范圍

1≤n≤200
1≤k≤n^2
1≤m≤20000,
圖中涉及邊長絕對值均不超過 10000

輸入樣例:

3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

輸出樣例:

impossible
1
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 210;
const int INF = 0x3f3f3f3f; 
// 用鄰接矩陣存儲
int d[N][N];
int n, m, k;
void floyd()
{
	for(int k = 1; k <= n; k++)
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= n; j++)
				d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	// 初試化邊權 
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
		{
			if(i == j) d[i][j] = 0;
			else 	   d[i][j] = INF;
		}
	// 
	while(m--)
	{
		int x, y, w;
		scanf("%d%d%d",&x, &y, &w);
		d[x][y] = min(d[x][y], w); // 對於重邊去最小的 
	}
	floyd();
	while(k--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		if(d[a][b] > INF / 2) printf("impossible\n");
		else 	        printf("%d\n",d[a][b]); 
	}
	return 0;
} 

Prim最小生成樹—稠密圖

圖中可能存在重邊和自環,邊權可能為負數。

  1. dist[i] <- INF
  2. for(i)
    1. 找到集合外距離最近的點t
    2. 用t更新其他點到集合的距離
    3. st[t] = true

要點其實是:每次貪心找到一條最小生成樹上的路徑

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m; 
int g[N][N]; // 建圖 
int dist[N]; // dist[i]表示i到集合的距離
bool st[N]; // 表示當前的點在不在集合內部 
int prim()
{
	memset(dist, 0x3f, sizeof dist); // 初始化所有點的距離到集合為INF 
	
	int res = 0;
	
	for(int i = 0; i < n; i++) // 循環n次,每次確定一個加入集合的點 
	{
		// 選擇集合外一個到集合最近的點 
		int t = -1;
		for(int j = 1; j <= n; j++)
			if(!st[j] && (t == -1 || dist[j] < dist[t]))
				t = j;
		
		if(i && (dist[t] == INF)) return INF; // 如果不是第一輪,並且集合外離集合最近的點的距離是INF,說明這個圖不連通,直接返回inf
		 
		if(i) res += dist[t]; // 不是第一論, 就把這條邊加到最小生成樹的路徑里面
		st[t] = true; // 這個點加入集合
		
		for(int k = 1; k <= n; k++) dist[k] = min(dist[k], g[t][k]); // 用t這個點更新其他點到集合的距離(集合內的點不用更新)
		
		// 上面先計算最短路,再更新其他點的順序不能變 
	}
	return res; 
}

int main()
{
	memset(g, 0x3f, sizeof g);
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		g[u][v] = g[v][u] = min(g[u][v], w); // 是無向圖;有重邊 
	}
	int t = prim();
	if(t == INF) printf("impossible");
	else 	     printf("%d", t);
	
	return 0;
}

Kruskal小生成樹—稀疏圖

給定一個 n 個點 m 條邊的無向圖,圖中可能存在重邊和自環,邊權可能為負數。

求最小生成樹的樹邊權重之和,如果最小生成樹不存在則輸出 impossible

給定一張邊帶權的無向圖 G=(V,E),其中 V 表示圖中點的集合,EE 表示圖中邊的集合,n=|V|,m=|E|。

由 V 中的全部 n 個頂點和 E 中 n−1 條邊構成的無向連通子圖被稱為 G 的一棵生成樹,其中邊的權值之和最小的生成樹被稱為無向圖 G 的最小生成樹。

prim使用鄰接表,適用於稠密圖(點的個數比較小的情況)

算法步驟

  • 將所有邊按照權重排序
  • 從小到大枚舉所有邊
    • 如果某條邊的兩個端點不連通的划(用並查集判斷是否連通),就把這條邊加入到最小生成樹,(並查集中連接這兩個點)
  • 最后如果邊的數量小於n-1,說明不連通,否則輸出最小生成樹長度
#include<iostream>
#include<cstdio>
#include <algorithm>

using namespace std;
const int  N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N]; // 並查集使用

struct Edge
{
    int a, b, w;
    bool operator< (const Edge W)const
    {
        return w < W.w;
    }
}edges[M];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges+m);
    int res = 0, cnt = 0; // cnt表示加入的邊數
    for(int i = 0; i < m; i++)
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        int fa = find(a), fb = find(b);
        if(fa != fb)
        {
            p[fa] = fb;
            res += w;
            cnt++;
        }
    }

    if(cnt < n-1) return INF;
    else    return res;

}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++)
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }

    for(int i = 1; i <= n; i++) p[i] = i; // 初始化並查集
    int  res = kruskal();
    if(res == INF) printf("impossible\n");
    else printf("%d\n", res);
    return 0;
}

二分圖的判定

使用染色法判斷二分圖

給定一個 n 個點 m 條邊的無向圖,圖中可能存在重邊和自環。

請你判斷這個圖是否是二分圖。

輸入格式

第一行包含兩個整數 n 和 m。

接下來 m 行,每行包含兩個整數 u 和 v,表示點 u 和點 v 之間存在一條邊。

輸出格式

如果給定圖是二分圖,則輸出 Yes,否則輸出 No

數據范圍

1≤n,m≤105

輸入樣例:

4 4
1 3
1 4
2 3
2 4

輸出樣例:

Yes
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N = 1e5 + 0;
const int M = N << 1;

int color[N]; // 1表示黑,2表示紅
int h[N], e[M], ne[M], idx = 0;
int n,m;

void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool dfs(int u, int c)
{
	color[u] = c;
	
	// 染色 
	for(int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(!color[j])
		{
			bool t = dfs(j, 3 - c);
			if(!t) return false;
		}
		else
		{
			if(color[j] == c) return false;	
		} 
	}
	return true;
}

bool bfs(int u)
{
	queue<int> q;
	if(!color[u]) color[u] = 1; // 從u開始bfs 
	q.push(u);
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		for(int i = h[t]; ~i; i = ne[i])
		{
			int j = e[i];	
			// if(st[j]) continue;
			
			// 沒染色就對他染色 
			if(!color[j])
			{
				color[j] = 3 - color[t];
				q.push(j);
			}
			else
			{
				if(color[j] == color[t]) return false;
			}
		}
	}
	return true;
}

int main()
{
	memset(h, -1, sizeof(h));
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b), add(b, a); // 無向圖 
	}
	
	bool flag = true;
/*
	for(int i = 1; i <= n; i++)
	{
		if(!color[i])
		{
			bool t = dfs(i, 1); 
			if(!t) 
			{
				flag = false;
				break;	
			}	
		}	
	}
*/ 
	for(int i = 1; i <= n; i++)
	{
		if(!color[i])
		{
			if(!bfs(i))
			{
				flag = false;
				break;
			}
		}
	}
	
	if(flag) puts("Yes");
	else 	 puts("No");
	return 0; 
} 

二分圖最大匹配——匈牙利算法

給定一個二分圖,其中左半部包含 n1 個點(編號 1∼n1),右半部包含 n2 個點(編號 1∼n2),二分圖共包含 mm 條邊。

數據保證任意一條邊的兩個端點都不可能在同一部分中。

請你求出二分圖的最大匹配數。

二分圖的匹配:給定一個二分圖 G,在 G 的一個子圖 M 中,M 的邊集 {E} 中的任意兩條邊都不依附於同一個頂點,則稱 M 是一個匹配。

二分圖的最大匹配:所有匹配中包含邊數最多的一組匹配被稱為二分圖的最大匹配,其邊數即為最大匹配數。

輸入格式

第一行包含三個整數 n1、 n2 和 m。

接下來 m 行,每行包含兩個整數 u 和 v,表示左半部點集中的點 u 和右半部點集中的點 v 之間存在一條邊。

輸出格式

輸出一個整數,表示二分圖的最大匹配數。

數據范圍

1≤n1,n2≤500
1≤u≤n1
1≤v≤n2
1≤m≤10^5

輸入樣例:

2 2 4
1 1
1 2
2 1
2 2

輸出樣例:

2
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 510, M = 1e5 + 10;
int h[N], e[M], ne[M], idx = 0;
bool st[N];
int math[N]; 
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool find(int x)
{
	for(int i = h[x]; i != -1; i = ne[i])
	{
		int j = e[i];
		
		// j沒有被訪問過 
		if(!st[j])
		{
			st[j] = true; // 這里為什么不用回溯? 
			if(math[j] == 0 || find(math[j]))
			{
				math[j] = x;
				return true;	
			} 
		}
	}
	
	return false;
}


int main()
{
	memset(h, -1, sizeof h);
	
	int n1, n2, m;
	scanf("%d%d%d", &n1, &n2, &m);
	for(int i = 1; i <= m; i++)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
	}
	int res = 0;
	for(int i = 1; i <= n1; i++)
	{
	    memset(st, 0, sizeof st);
	    if(find(i)) res++;
	}
// 		if(find(i)) res++;
		
	printf("%d\n", res);
	return 0;
} 

數學知識

快速冪

給定 n 組 ai,bi,pi,對於每組數據,求出 a^b % p 的值。

typedef long long ll;
ll ksm(int a, int b, int p)
{
    ll res = 1;
    ll t = a;
    while(b)
    {
        if(b & 1) res = (res * t) % p; // 判斷每一位 

        t = (t * t) % p; // 累乘 
        b >>= 1;
    }
    return res;
}

逆元

給定 n 組 ai,pi其中 pi是質數,求 ai 模 pi 的乘法逆元,若逆元不存在則輸出 impossible。

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

typedef long long ll;

ll ksm(int a, int b, int p)
{
    ll res = 1;
    ll t = (ll)a;
    while(b)
    {
        if(b&1) res = (res * t) % p;

        t = (t * t) % p;
        b >>= 1;
    }
    return res;
}

int main()
{
    int n;
    int a, p;

    scanf("%d", &n);
    while(n--)
    {
        scanf("%d%d", &a, &p);
        if(a % p == 0) puts("impossible"); // 數據保證了p是質數,判斷a和p是否互質,只需要看p是不是a的因子
        else    printf("%d\n", ksm(a, p-2, p));
    }
    return 0;

}

試除法判斷質數

給定 n 個正整數 ai,判定每個數是否是質數。

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

bool isPrim(int x)
{
    if(x < 2) return false;
    for(int i = 2; i <= x / i; i++) // 一定是 <=
    {
        if(x % i == 0) return false;
    }

    return true;
}

int main()
{
    int n;
    scanf("%d", &n);
    while(n --)
    {
        int x;
        scanf("%d", &x);
        if(isPrim(x)) cout << "Yes" << endl;
        else          cout << "No" << endl;
    }
    return 0;
}

分解質因數

給定 n 個正整數 ai,將每個數分解質因數,並按照質因數從小到大的順序輸出每個質因數的底數和指數。

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

int main()
{
    int n;
    scanf("%d", &n);
    while(n --)
    {
        int x;
        scanf("%d", &x);

        for(int i = 2; i <= x / i; i++)
        {

            if(x % i == 0)  // 說明i是x的質因數,如果2是x的質因數,那么當i=4的時候一定不是此時x的因數,所以不必擔心
            {
                int cnt = 0;
                while(x % i == 0)
                {
                    cnt ++;
                    x /= i;
                }
                printf("%d %d\n", i, cnt);
            }
        }
        if(x > 1) printf("%d 1\n", x, 1);
        
        puts("");
    }

    return 0;
}

質數篩

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

const int N = 1000010;
int cnt = 0;
int primes[N];
int st[N]; // st[i] = false 表示是質數


// 朴素篩法
void get_primes(int n) // 得到從1到n的所有質數
{
    for(int i = 2; i <= n; i++)
    {
        if(st[i]) continue; // 標記過了,說明是合數,直接跳過
        
        primes[cnt ++] = i; // 沒有標記,說明i是質數
        //把質數i的所有倍數都篩掉
        for(int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}

// 線性篩法
void get_primes_2(int n)
{
    for(int i = 2; i <= n; i++)
    {
        if(!st[i]) primes[cnt++] = i;
        
        for(int j = 0; primes[j] <= n / i; j++)
        {
            st[i * primes[j]] = true;
            if(i % primes[j] == 0) break;
        }
    }
    
}


int main()
{
    int n;
    cin >> n;
    get_primes_2(n);
    cout << cnt << endl;
    return 0;
}


最大公約數

求a, b 的最大公約數,輾轉相除法

int gcd(int a, int b)
{
    return b ? gcd(b, a%b) : a;
}

//int gcd(int a,int b)   //最大公約數
//{
//    if(b==0)  return a;
//    else return gcd(b,a%b);
//}

         
int lcm(int a,int b)  //最小公倍數
{
    return a/gcd(a,b)*b;    //防止溢出
}

求組合數

給定 n 組詢問,每組詢問給定兩個整數 a,b,請你輸出 c[a][b] % mod 的值。

主要根據數據范圍來選擇使用什么算法

  1. 當 a,b <= 2000:直接預處理得到所有2000以內的c[a][b],使用遞推公式:\(c_a^b = c_{a-1}^b + c_{a-1}^{b-1}\),這個很好理解,假設籃子里有a個蘋果,其中有一個紅蘋果,根據選紅蘋果或者不選分為兩類,就是上面兩個公式;注意當b=0的時候,有\(c_a^0 = 1\)

  2. 當a,b <= 100000:使用 \(c_a^b = \frac{a!}{b! * (a-b)!} \% mod= (a! * (b!)^{-1} * (a-b!)^{-1}) % mod\),其中-1表示的是逆元(可以用快速冪來求)

  3. 當a,b <= 10^18:使用盧卡斯定理\(C_a^b = C_{a\%p}^{b\%p} * C_{a/p}^{b/p} (\% p)\)

    typedef long long ll;
    ll lucas(ll a, ll b, int p)
    {
        if(a < p && b < p) return C(a, b, p);
        return C(a%p, b%p, p) * lucas(a/p, b/p, p) % p; // 這里是遞歸
    }
    

情況4:更大(暫時不看了)需要高精度

情況1:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2010;
const int mod = 1e9 + 7;
int c[N][N];
int n;

void init() // 打表把所有2000以內的c[i][j]都求出來
{
    for(int i = 0; i < N; i++) 
    {
        for(int j = 0; j <= i; j++) 
        {
            if(!j) c[i][j] = 1; // 當j=0,就是從i個里面選0個,一共1種選法
            
            else   c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod;
        }
    }
}

int main()
{
    scanf("%d", &n);
    
    init();
    while(n --)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        
        cout << c[a][b] << endl;
    }
    return 0;
}

情況2

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

typedef long long ll;

const int N = 1e5 + 10;
const int mod = 1e9 + 7;

ll fact[N]; // 階乘
ll infact[N]; // 某個階乘的逆元

ll ksm(int a, int b, int p)
{
    ll res = 1;
    ll t = a;
    while(b)
    {
        if(b & 1) res = (res * t) % mod;
        
        t = (t * t) % mod;
        b >>= 1;
    }
    return res;
}

int main()
{
    int n;
    scanf("%d", &n);
    
    // 初始化
    fact[0] = infact[0] = 1;
    // 求階乘
    for(int i = 1; i <= N; i++)
    {
        fact[i] = (fact[i-1] * i) % mod;
        infact[i] = ksm(fact[i], mod - 2, mod) % mod;
    }
    
    
    while(n--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%lld\n", (fact[a] * infact[b] % mod ) * infact[a-b] % mod );
    }
    return 0;
    
}

情況3

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

typedef long long ll;

int p; // 定義成全局變量,因為各個函數都用得到 

ll ksm(int a, int b, int p)
{
	ll res = 1;
	ll t = a;
	while(b)
	{
		if(b & 1) res = (res * t) % p;
		
		t = (t * t) % p;
		b >>= 1;
	}
	return res;
}


// 要保證 a < p && b < p 才有逆元 
ll C(int a, int b, int p)
{
	if(a < b) return 0;// c_a ^ b
	
	ll res = 1;
	// j: a -> a-b+1,  i: 1 -> b
	for(int i = 1, j = a; i <= b; i++, j--)
	{
		res = (ll)(res * j) % p; // res * j
		res = (ll)(res * ksm(i, p-2, p)) % p; // res * j * inv(i)
	}
	return res;	
}


ll lucas(ll a, ll b, int p)
{
	if(a < p && b < p) return C(a, b, p);
	
	return C(a%p, b%p, p) * lucas(a/p, b/p, p) % p;  // 盧卡斯定理 
}

int main()
{
	int n;
	scanf("%d", &n);
	while(n--)
	{
		ll a, b, p;
		scanf("%lld%lld%lld", &a, &b, &p);
		printf("%lld\n", lucas(a, b, p));
	}
	return 0;
} 

from yxc

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;


int qmi(int a, int k, int p)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}


int C(int a, int b, int p)
{
    if (b > a) return 0;

    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j -- )
    {
        res = (LL)res * j % p;
        res = (LL)res * qmi(i, p - 2, p) % p;
    }
    return res;
}


int lucas(LL a, LL b, int p)
{
    if (a < p && b < p) return C(a, b, p);
    return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}


int main()
{
    int n;
    cin >> n;

    while (n -- )
    {
        LL a, b;
        int p;
        cin >> a >> b >> p;
        cout << lucas(a, b, p) << endl;
    }

    return 0;
}

擴展歐幾里得算法

給定 n 對正整數 ai,bi,對於每對數,求出一組 xi,yi,使其滿足 \(a_i×x_i+b_i×y_i=gcd(a_i,b_i)\)

輸入格式

第一行包含整數 n。

接下來 n 行,每行包含兩個整數 ai,bi。

輸出格式

輸出共 n 行,對於每組ai,bi,求出一組滿足條件的 xi,yi,每組結果占一行。

本題答案不唯一,輸出任意滿足條件的 xi,yi 均可。

數據范圍

1≤n≤105
1≤ai,bi≤2×109

輸入樣例:

2
4 6
8 18

輸出樣例:

-1 1
-2 1
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int n;

int gcd(int a, int b)
{
    if(!b) return a;
    
    return gcd(b, a % b);
}

int exgcd(int a, int b, int &x, int &y)
{
    if(!b)
    {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main()
{
    scanf("%d", &n);
    while(n --)
    {
        int a, b, x, y;
        scanf("%d%d", &a, &b);
        exgcd(a, b, x, y);
        printf("%d %d\n", x, y);
    }
    return 0;
}

歐拉函數

#include<iostream>
using namespace std;

int phi(int x)
{
    int res = x;
    for(int i = 2; i <= x/i; i++)
    {
        // 找到一個質因子
        if(x % i == 0)
        {
            res = res / i * (i-1); // 即 res*(1 - 1/i);
            while(x % i == 0) x /= i;
        }
    }
    if(x > 1) res = res / x * (x - 1);
    return res;
}


int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int x;
        cin >> x;
        cout << phi(x) << xendl;
    }

    return 0;
}

線性篩法求歐拉函數

給定一個正整數 n,求 1∼n 中每個數的歐拉函數之和。

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1000010;


int primes[N], cnt;
int euler[N];
bool st[N];


void get_eulers(int n)
{
    euler[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            euler[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0)
            {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}


int main()
{
    int n;
    cin >> n;

    get_eulers(n);

    LL res = 0;
    for (int i = 1; i <= n; i ++ ) res += euler[i];

    cout << res << endl;

    return 0;
}

作者:yxc
鏈接:https://www.acwing.com/activity/content/code/content/49995/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

NIM游戲

給定 n 堆石子,兩位玩家輪流操作,每次操作可以從任意一堆石子中拿走任意數量的石子(可以拿完,但不能不拿),最后無法進行操作的人視為失敗。

問如果兩人都采用最優策略,先手是否必勝。

輸入格式

第一行包含整數 n。

第二行包含 n 個數字,其中第 i 個數字表示第 i 堆石子的數量。

輸出格式

如果先手方必勝,則輸出 Yes

否則,輸出 No

數據范圍

1≤n≤105
1≤每堆石子數≤109

輸入樣例:

2
2 3

輸出樣例:

Yes

#include<iostream>
using namespace std;
int n;

int main()
{
    scanf("%d", &n);
    int res = 0;
    for(int i = 0; i < n; i++)
    {
        int a; 
        scanf("%d", &a);
        res ^= a;
    }
    if(res) puts("Yes");
    else    puts("No");
    return 0;
}

動態規划

數字三角形

給定一個如下圖所示的數字三角形,從頂部出發,在每一結點可以選擇移動至其左下方的結點或移動至其右下方的結點,一直走到底層,要求找出一條路徑,使路徑上的數字的和最大。

     7
   3   8
 8   1   0
2   7   4   4
4   5   2   6   5

輸入格式

第一行包含整數 n,表示數字三角形的層數。

接下來 n 行,每行包含若干整數,其中第 i 行表示數字三角形第 i 層包含的整數。

輸出格式

輸出一個整數,表示最大的路徑數字和。

輸入樣例:

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

輸出樣例:

30
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 510;
int a[N][N], f[N][N];

int INF = 1e9;

int main()
{
    
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= i; j++) 
            scanf("%d", &a[i][j]);
    
    // 初始化
    // 注意初始化的時候要把周圍的都初始化
    for(int i = 0; i <= n; i++)// 一定要到i+1
        for(int j = 0; j <= i + 1; j++) f[i][j] = -INF;
    
    f[1][1] = a[1][1];
    // 從上往下!
    for(int i = 2; i <= n; i++) 
        for(int j = 1; j <= i; j++) 
        {
            f[i][j] = max(f[i-1][j-1], f[i-1][j]) + a[i][j];
        }
    
    int res = -INF;
    for(int i = 1; i <= n; i++) res = max(res, f[n][i]);
    
    printf("%d\n", res);
    return 0;
    
}

背包

01背包

有 N 件物品和一個容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的體積是 vi,價值是 wi。

求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。
輸出最大價值。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d", &v[i], &w[i]);
    }
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            f[i][j] = f[i-1][j];
            if(j >= v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
        }
    }
    printf("%d\n", f[n][m]);
    return 0;
    
}

一維優化

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d", &v[i], &w[i]);
    }
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = m; j >= v[i]; j--)
            f[j] = max(f[j], f[j-v[i]] + w[i]);
    }
    printf("%d\n", f[m]);
    return 0;
    
}

完全背包

求最大值

有 N 種物品和一個容量是 V 的背包,每種物品都有無限件可用。

第 i 種物品的體積是 vi,價值是 wi。

求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。
輸出最大價值。

二維

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

const int N = 1010;
int f[N][N];
int v[N], w[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d%d", &v[i], &w[i]);
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            f[i][j] = f[i-1][j];
            if(j >= v[i])
                f[i][j] = max(f[i][j] , f[i][j-v[i]] + w[i]); 
            
            // for(int k = 0; k * v[i] <= j; k++)
            // {
            //     f[i][j] = max(f[i][j], f[i-1][j-k*v[i]] + k*w[i]);
            // }
        }
    }
    printf("%d\n", f[n][m]);
    return 0;
}

一維優化

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];

    for (int i = 1; i <= n; i ++ )
        for (int j = v[i]; j <= m; j ++ )
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[m] << endl;

    return 0;
}

作者:yxc
鏈接:https://www.acwing.com/activity/content/code/content/57825/
來源:AcWing
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
求總方案數

題目描述

一個正整數n可以表示成若干個正整數之和,形如:n=n1+n2+…+nk,其中n1≥n2≥…≥nk,k≥1

我們將這樣的一種表示稱為正整數n的一種划分。

現在給定一個正整數n,請你求出n共有多少種不同的划分方法。

輸入格式

共一行,包含一個整數n。

輸出格式

共一行,包含一個整數,表示總划分數量。

由於答案可能很大,輸出結果請對10^9+7取模。

數據范圍

1≤n≤1000

輸入樣例:

5
輸出樣例:

7

代碼

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

const int N = 1010;
const int mod = 1e9+7;

int n;
int f[N][N];

int main()
{
    scanf("%d", &n);
    
    // 初始化
    f[0][0] = 1;
    
    //
    for(int i = 1; i <= n; i++) // 前i件物品
    {
        for(int j = 0; j <= n; j++) // 容量為j;一定從0開始
        {
            for(int k = 0; k * i <= j; k++) // 第i件物品選k件
            {
                f[i][j] = (f[i][j] + f[i-1][j-k*i]) % mod;
            }
        }
    }
    
    printf("%d\n", f[n][n]);
    return 0;
}

多重背包

有 N 種物品和一個容量是 V 的背包。

第 i 種物品最多有 si 件,每件體積是 vi,價值是 wi。

求解將哪些物品裝入背包,可使物品體積總和不超過背包容量,且價值總和最大。
輸出最大價值。

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

const int N = 110;

int n, m;
int f[N][N], v[N], w[N], s[N];

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) cin >> v[i] >> w[i] >> s[i];
	
	for(int i = 1; i <= n; i++) // 前i件物品 
		for(int j = 0; j <= m; j++) // 背包容量 
		{
			for(int k = 0; k <= s[i] && k * v[i] <= j; k++)
			    f[i][j] = max(f[i][j], f[i-1][j-k*v[i]]+k*w[i]);
		} 
	
	cout << f[n][m] << endl; 
	
	return 0;
}
 

二進制優化

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

const int N = 12010; // n*log(s)

int cnt = 0; // 每種物品拆分后,一共形成的組數,最后就是01背包問題
int v[N], w[N];
int n, m;


int f[N]; // 01背包的一維優化,f[i]表示的是

int main()
{
    scanf("%d%d", &n, &m);
    
    for(int i = 1; i <= n; i++)
    {
        int vx, wx, s;
        scanf("%d%d%d", &vx, &wx, &s);
        
        int k = 1;
        while(k <= s) // 這里是 k <= s
        {
            cnt++; // 總組數+1
            v[cnt] = vx * k;
            w[cnt] = wx * k;
            
             
            s -= k;
            k *= 2;
        }
        
        // 如果s按照 1 2 4 8 …… 拆分后還剩下,就直接加入到里面
        if(s > 0)
        {
            cnt++; // 總組數+1
            v[cnt] = vx * s;
            w[cnt] = wx * s;
            
        }
    }
    
    n = cnt; // 拆分后相當於現在有cnt個物品了,然后決定每個物品選還是不選
    
    // 把所有的物品都拆分了,放入到了v[], w[]中,下面就可以用01背包來遍歷實現最大值了
    for(int i = 1; i <= n; i++) //
        for(int j = m; j >= v[i]; j--)
        {
            f[j] = max(f[j], f[j-v[i]] + w[i]);
        }

    printf("%d\n", f[m]);
    return 0;
    
}

分組背包

有 N 組物品和一個容量是 V 的背包。

每組物品有若干個,同一組內的物品最多只能選一個。
每件物品的體積是 vij,價值是 wij,其中 i 是組號,j 是組內編號。

求解將哪些物品裝入背包,可使物品總體積不超過背包容量,且總價值最大。

輸出最大價值。

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

const int N = 110;

int n, m;
int v[N][N], w[N][N]; // v[i][j] 表示第i組第j個物品
int s[N];

int f[N]; // f[i][j] 表示前i組物品,容量為j的情況下的最大值

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        
        scanf("%d", &s[i]);
        
        for(int j = 1; j <= s[i]; j++)
        {
            scanf("%d%d", &v[i][j], &w[i][j]);
        }
    }
    
    for(int i = 1; i <= n; i++) // 循環分組
    
        for(int j = m; j >= 0; j--) // 注意體積從大到小枚舉!!01背包
        {
            for(int k = 1; k <= s[i]; k++) // 每個分組內部選擇第k個物品
            {
                if(j >= v[i][k]) f[j] = max(f[j], f[j-v[i][k]] + w[i][k]);
            }
        }
    cout << f[m];
    return 0;
    
}

區間DP

例題:acwing 282. 石子合並

設有 N 堆石子排成一排,其編號為 1,2,3,…,N。

每堆石子有一定的質量,可以用一個整數來描述,現在要將這 N 堆石子合並成為一堆。

每次只能合並相鄰的兩堆,合並的代價為這兩堆石子的質量之和,合並后與這兩堆石子相鄰的石子將和新堆相鄰,合並時由於選擇的順序不同,合並的總代價也不相同。

例如有 4 堆石子分別為 1 3 5 2, 我們可以先合並 1、2 堆,代價為 4,得到 4 5 2, 又合並 1,2 堆,代價為 9,得到 9 2 ,再合並得到 11,總代價為 4+9+11=24;

如果第二步是先合並 2,3 堆,則代價為 7,得到 4 7,最后一次合並代價為 11,總代價為 4+7+11=22。

問題是:找出一種合理的方法,使總的代價最小,輸出最小代價。

輸入格式
第一行一個數 N 表示石子的堆數 N。

第二行 N 個數,表示每堆石子的質量(均不超過 1000)。

輸出格式
輸出一個整數,表示最小代價。

數據范圍
1≤N≤300
輸入樣例:
4
1 3 5 2
輸出樣例:
22

模板

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

const int N = 310;
const int INF = 0x3f3f3f3f;
int a[N], s[N]; // s[i]是前i堆石子的前綴和
int f[N][N]; // f[i][j] 表示的是從第i堆合並到第j堆,最小代價
int n;
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    // 計算合並的前綴和
    for(int i = 1; i <= n; i++) s[i] = s[i-1] + a[i];
    
    // 初始化, 區間長度為1時
    for(int i = 1; i <= n; i++) f[i][i] = 0;
    
    // 要按照區間從小到大得到 f[1][n],直接從2開始
    for(int len = 2; len <= n; len++) // k表示枚舉區間的長度
    {
        for(int i = 1; i + len -1 <= n; i++) //枚舉區間的左端點
        {
            int l = i, r = i + len - 1; // 枚舉左右端點
            f[l][r] = INF;
            for(int k = l; k < r; k++)
            {
                f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] + s[r] - s[l-1]);  
            }
        }
    }
    printf("%d\n", f[1][n]);
    return 0;
}

貪心

模擬

心態一定要穩!

頭文件模板

#include<bits/stdc++.h>
// zwh模板
#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
#include<unordered_map>
#include<cstdlib>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;


priority_queue<PII, vector<PII>, greater<PII>> heap;

STL使用

小根堆的定義

priority_queue<PII, vector<PII>, greater<PII>> heap;

穩定排序

bool cmp(int a, int b)
{
    return a < b;
}
stable_sort(a, a+n, cmp);

C++ 標准庫cmath數學函數大全

C++頭文件聲明了一組函數來執行數學運算,例如:sqrt()計算平方根,log()查找數字的自然對數,等等。

方法 描述
abs() 返回參數的絕對值
acos() 返回反余弦數字
acosh() 返回數字的雙曲余弦值
asin() 返回反正弦值
asinh() 返回數字的雙曲正弦值
atan() 返回反正切數字
atan2() 返回坐標的反正切
atanh() 返回數字的弧雙曲正切
cbrt() 計算數字的立方根
ceil() 返回數字的上限值
copysign(x,y) 它以y的符號返回x的大小。
cos() 返回參數的余弦
cosh() 返回某個角度的雙曲余弦值
exp() 它計算升為冪x的指數e。
exp2() 它計算x的以2為底的指數。
expm1() 它計算出冪乘以x減一的指數。
fabs() 返回參數的絕對值
fdim(x,y) 返回x和y之間的正差。
floor() 返回十進制數字的下限值
fma(x,y,z) 它計算表達式x * y + z。
fmax() 返回傳遞的兩個參數中最大的
fmin() 返回兩個給定參數中的最小值
fmod() 計算除法浮點數的余數
frexp() 返回一個浮點數的尾數和指數。
hypot() 返回參數平方和的平方根
ilogb() 返回| x |的對數的整數部分
ldexp() 將x和2的乘積返回到冪e
llrint() 使用當前舍入模式舍入參數
llround() 將參數四舍五入到最接近的long long int值
log() 返回數字的自然對數
log10() 返回數字的以10為底的對數
log1p() 返回x + 1的自然對數。
log2(x) 它計算x的以2為底的對數。
logb(x) 返回| x |的對數
lrint() 使用當前舍入模式舍入參數
lround() 返回最接近參數的long int值
modf() 將數字分解為整數和小數部分
nan() 返回NaN值
nearbyint() 將參數舍入為使用當前舍入模式
nextafter() 它表示x在y方向上的下一個可表示值。
nexttoward() 它表示x在y方向上的下一個可表示值。
pow() 計算冪
restder(x,y) 返回x / y的余數
remquo(x,y) 計算機余數並存儲x / y的商
rint() 使用當前舍入模式舍入參數
round() 返回最接近參數的整數值
scalbln(x,n) 計算x和FLT_RADX乘以n的乘積。
scalbn(x,n) 計算x和FLT_RADX乘以n的乘積。
sin() 返回參數的正弦
sinh() 返回某個角度的雙曲正弦
sqrt() 計算數字的平方根
tan() 返回參數的切線
tanh() 返回角度的雙曲正切
trunc() 截斷數字的符號部分

bitset

bitset存儲二進制數位。
bitset就像一個bool類型的數組一樣,但是有空間優化——bitset中的一個元素一般只占1 bit,相當於一個char元素所占空間的八分之一。
bitset中的每個元素都能單獨被訪問,例如對於一個叫做foo的bitset,表達式foo[3]訪問了它的第4個元素,就像數組一樣。
bitset有一個特性:整數類型和布爾數組都能轉化成bitset。
bitset的大小在編譯時就需要確定。如果你想要不確定長度的bitset,請使用(奇葩的)vector

構造函數

#include<bitset>
std::bitset<4> foo; //創建一個4位的位集,每一位默認為0
當整數的大小小於位數時,高位填充為0
std::bitset<4> foo(5);  //用整數初始化  5二進制位:101    foo值:0101
當整數的大小超過位數時,從整數二進制的低位開始賦值,高位被舍棄
std::bitset<4> foo(19);  //用整數初始化,19二進制位:10011     foo值:1100
std::bitset<4> foo(std:;string("0101")); //字符串初始化,字符串中必須只能含有‘0’/‘1’
————————————————
版權聲明:本文為CSDN博主「風知我意否」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/ywh15387127537/article/details/88707044

用法

位運算都可以用: 與、或、非、異或,左移,右移
foo&foo2
foo|foo2
~foo
foo^foo2
foo<<=2
foo>>=2
foo.size() 返回大小(位數)
foo.count() 返回1的個數
foo.any() 返回是否有1
foo.none() 返回是否沒有1
foo.set() 全都變成1
foo.set(p) 將第p + 1位變成1
foo.set(p, x) 將第p + 1位變成x
foo.reset() 全都變成0
foo.reset(p) 將第p + 1位變成0
foo.flip() 全都取反
foo.flip(p) 將第p + 1位取反
foo.to_ulong() 返回它轉換為unsigned long的結果,如果超出范圍則報錯
foo.to_ullong() 返回它轉換為unsigned long long的結果,如果超出范圍則報錯
foo.to_string() 返回它轉換為string的結果
————————————————
版權聲明:本文為CSDN博主「風知我意否」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/ywh15387127537/article/details/88707044

容器

vector, 變長數組,倍增的思想
    size()  返回元素個數
    empty()  返回是否為空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    支持比較運算,按字典序

pair<int, int>
    first, 第一個元素
    second, 第二個元素
    支持比較運算,以first為第一關鍵字,以second為第二關鍵字(字典序)

string,字符串
    size()/length()  返回字符串長度
    empty()
    clear()
    substr(起始下標,(子串長度))  返回子串
    c_str()  返回字符串所在字符數組的起始地址

queue, 隊列
    size()
    empty()
    push()  向隊尾插入一個元素
    front()  返回隊頭元素
    back()  返回隊尾元素
    pop()  彈出隊頭元素

priority_queue, 優先隊列,默認是大根堆
    size()
    empty()
    push()  插入一個元素
    top()  返回堆頂元素
    pop()  彈出堆頂元素
    定義成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;

stack, 棧
    size()
    empty()
    push()  向棧頂插入一個元素
    top()  返回棧頂元素
    pop()  彈出棧頂元素

deque, 雙端隊列
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()
    []

set, map, multiset, multimap, 基於平衡二叉樹(紅黑樹),動態維護有序序列
    size()
    empty()
    clear()
    begin()/end()
    ++, -- 返回前驅和后繼,時間復雜度 O(logn)

    set/multiset
        insert()  插入一個數
        find()  查找一個數
        count()  返回某一個數的個數
        erase()
            (1) 輸入是一個數x,刪除所有x   O(k + logn)
            (2) 輸入一個迭代器,刪除這個迭代器
        lower_bound()/upper_bound()
            lower_bound(x)  返回大於等於x的最小的數的迭代器
            upper_bound(x)  返回大於x的最小的數的迭代器
    map/multimap
        insert()  插入的數是一個pair
        erase()  輸入的參數是pair或者迭代器
        find()
        []  注意multimap不支持此操作。 時間復雜度是 O(logn)
        lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
    和上面類似,增刪改查的時間復雜度是 O(1)
    不支持 lower_bound()/upper_bound(), 迭代器的++,--

bitset, 圧位
    bitset<10000> s;
    ~, &, |, ^
    >>, <<
    ==, !=
    []

    count()  返回有多少個1

    any()  判斷是否至少有一個1
    none()  判斷是否全為0

    set()  把所有位置成1
    set(k, v)  將第k位變成v
    reset()  把所有位變成0
    flip()  等價於~
    flip(k) 把第k位取反

常用STL總結

一、棧(stack)

stack實現了一種先進后出的數據結構,使用時需要包含stack頭文件

C++定義stack語法:
stack<int> s;//int為棧的數據類型,可以為string,double等
C++中stack的基本操作有:

1、出棧:如 s.pop() 注意並不返回出棧的元素
2、進棧:如 s.push(x)
3、訪問棧頂元素:如s.top();
4、判斷棧空:如 s.empty() 棧為空時返回true
5、返回棧中元素個數,如:s.size()

#include <iostream>
#include <stack>
using namespace std;
 
int main()
{
    std::stack<int> s;      //定義一個int類型的stack
    for(int i=0;i<10;i++)   //將數字0~9依次入棧
        s.push(i);
 
    std::cout << s.empty() << std::endl; //將輸出0,表示棧不為空
 
    for(int i=0;i<10;i++)
    {
        std::cout << s.top() << std::endl;//輸出棧頂的元素,依次為9、8、...、0
        s.pop();    //出棧
    }
    std::cout << s.empty() << std::endl;//將輸出1,表示棧為空
    return 0;
}

二、動態數組(vector)

C++中的vector是一個可以改變大小的數組,當解題時無法知道自己需要的數組規模有多大時可以用vector來達到最大節約空間的目的。使用時需要包含vector頭文件。

定義一個一維動態數組的語法為
vector<int> a; //int為該動態數組的元素數據類型,可以為string、double等
定義一個二維動態數組的語法為
vector<int*> a; //三維數據類型為int**,以此類推。
C++中vector的基本操作有:
1、push_back(x) 在數組的最后添加元素x。
2、pop_back() 刪除最后一個元素,無返回值。
3、at(i) 返回位置i的元素。
4、begin() 返回一個迭代器,指向第一個元素。
5、end() 返回一個迭代器,指向最后一個元素的下一個位置。
6、front() 返回數組頭的引用。
7、capacity(x) 為vector分配空間
8、size() 返回數組大小
9、resize(x) 改變數組大小,如果x比之前分配的空間大,則自動填充默認值。
10、insert 插入元素
①a.insert(a.begin(),10); 將10插入到a的起始位置前。
②a.insert(a.begin(),3,10) 將10插入到數組位置的0-2處。
11、erase 刪除元素
①a.erase(a.begin()); 將起始位置的元素刪除。
②a.erase(a.begin(),begin()+2); 將0~2之間的元素刪除。
12、rbegin() 返回一個逆序迭代器,它指向最后一個元素。
13、rend() 返回一個逆序迭代器,它指向的第一個元素前面的位置。
14、clear()清空所有元素。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
int main()
{
    vector<int> a;
    vector<int> b;
 
    for (int i = 0; i < 10; i++)    //向數組a依次添加0~9
        a.push_back(i);
 
    a.swap(b);      //將數組a元素與數組b元素交換
    cout << a.size() << " " << b.size() << endl;    //此時應當輸出 0 10
 
    for (vector<int>::iterator it = b.begin(); it != b.end(); it++)//從第一個元素開始遍歷數組元素
        cout << *it << " ";     //依次輸出0~9
    cout << endl;
 
    b.erase(b.begin() + 1);     //刪除位置1的元素,即元素1.
    cout << b.size() << endl;   //由於刪除了一個元素,此時輸出應當為8
 
    for (vector<int>::reverse_iterator rit = b.rbegin(); rit != b.rend(); ++rit)//逆向輸出數組元素
        cout << *rit << " ";    //應當輸出9 8 7 6 5 4 3 2 0
    cout << endl;
 
    b.resize(9);    //將數組空間設定為9,相當於比之前多了1個位置
    b.push_back(20);//在尾部添加元素20
 
    for (vector<int>::iterator it = b.begin(); it != b.end(); it++)
        cout << *it << " ";  //應當輸出0 2 3 4 5 6 7 8 9 20
 
    return 0;
}

三、集合(set)

C++中集合(set)類似於數學上的集合,即每個元素只能出現一次,使用該容器需要包含set頭文件。

定義一個set的語法為:
set<int> s; //int為集合的數據類型,可以為string,double等
C++中set的基本操作有:
1、begin() 返回一個迭代器,指向第一個元素。
2、end() 返回一個迭代器,指向最后一個元素的下一個位置。
3、clear()清空set的所有元素。
4、empty() 判斷是否為空。
5、size() 返回當前元素個數
6、erase(it) 刪除迭代器指針it指向的元素。
7、insert(a) 插入元素a
8、count() 查找某個元素出現的次數,只有可能為0或1。
9、find() 查找某個元素出現的位置,如果找到則返回這個元素的迭代器,如果不存在,則返回s.end()

四、隊列(queue)

queue實現了一種先進先出的數據結構,使用時需要包含queue頭文件。

定義一個queue的語法為:
queue<int> q; //int為隊列的數據類型,可以為string,double等
C++中queue的基本操作有:
1、入隊,如:q.push(x) 將元素x置於隊列的末端
2、出隊,如: q.pop() 同樣不會返回彈出元素的值
3、返回隊首元素,如:q.front();
4、返回隊尾元素,如:q.back();
5、判斷是否為空,如:q.empty();
6、返回隊列元素個數,如:q.size();

#include <iostream>
#include <queue>
using namespace std;
 
int main()
{
    queue<int> q;
    for(int i = 0;i < 10;i++)   //將0~9依次入隊
        q.push(i);
 
    cout << q.front() << " " << q.back() << endl; //這里應當輸出0和9
 
    //依次輸出0、1、...、9
    for(int i = 0;i < 10;i++)
    {
        cout << q.front() << " ";
        q.pop();
    }
    return 0;
}

C++ STL

本文整理了常用到的C++ STL,用於自查。
(要想更好的 A 題,還需要對這些知識的熟練掌握和靈活運用。
現整理如下

// size和empty是所有容器都有的方法
    size()  返回元素個數
    empty()  返回是否為空

vector

vector
// 變長數組,自動增長
    // 定義 一個長度為10的vector 每一個值為-3
    vector<int> a(10, -3);
	vector<int> a[10];
	// size和empty是所有容器都有的方法
    size()  返回元素個數
    empty()  返回是否為空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    // 迭代器
    begin()/end()
    // 支持比較運算,按字典序對所有元素排序
        
// 示例代碼:
int main() {
	vector<int> v;
	for(int i = 0; i < 10; i++) {
		v.push_back(i);
	}
	for(int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;
	for(vector<int>::iterator i = v.begin(); i != v.end(); i++) {
		cout << *i << " ";
	}
	cout << endl;
	for(auto x: v) {
		cout << x << " ";
	}
	cout << endl;
	return 0;
}    

pair

pair<int, string>
// 存儲二元組 適用於頂替有比較的結構體
    p.first; 第一個元素
    p.second, 第二個元素
    // 支持比較運算,以first為第一關鍵字,以second為第二關鍵字(字典序)
    p = make_pair(10, "zjy");
	p = {20, "zjy"};

string

string
// 字符串
    size()/length()  返回字符串長度
    empty()
    clear()
    substr(起始下標,子串長度)  返回子串
    c_str()  返回字符串所在字符數組的起始地址
    // c_str可以用來輸出
    printf("%s", s.c_str);
	
	back()	取最后一個元素
	push_back(c)
	pop_back()

queue

queue,
// 隊列
    size()
    empty()
    push()  向隊尾插入一個元素
    front() 返回隊頭元素
    back() 	返回隊尾元素
    pop() 	彈出隊頭元素
    // 如果向清空這個隊列q,重新構造即可
    queue<int>();

priority_queue

priority_queue
// 優先隊列,默認是大根堆
    size()
    empty()
    push()  插入一個元素
    top()  返回堆頂元素
    pop()  彈出堆頂元素
    // 定義成小根堆的方式
    priority_queue<int, vector<int>, greater<int>> q;

stack

stack
// 棧
    size()
    empty()
    push()	向棧頂插入一個元素
    top() 	返回棧頂元素
    pop()	彈出棧頂元素

deque

deque, 雙端隊列
// 使用較少 效率較低
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()

set

set/multiset
// set里不能有重復元素
// multiset可以有重復元素
    insert()	插入一個數
    find()		查找一個數
    count() 	返回某一個數的個數
    erase()
    (1) 輸入是一個數x,刪除所有x   
    	O(k + logn)
    (2) 輸入一個迭代器,刪除這個迭代器
    lower_bound()/upper_bound()
    // 返回大於等於x的最小的數的迭代器
    lower_bound(x)  
    // 返回大於x的最小的數的迭代器
    upper_bound(x)  

map/multimap

map/multimap
    insert()	參數是一個pair
    erase() 	參數是pair或者迭代器
    find()
    []   
/*
int main() {
	map<string, int> mapp;
	mapp["zjy"] = 14;
	cout << mapp["zjy"] << endl;
	return 0;
}
multimap不支持此操作 時間復雜度是 O(logn)
*/
    lower_bound()/upper_bound()

set, map, multiset, multimap, 基於平衡二叉樹(**紅黑樹**),本質是動態維護有序序列
    size()
    empty()
    clear()
    begin()/end()
    ++, -- 返回前驅和后繼,時間復雜度 O(logn)
    unordered_set, unordered_map, unordered_multiset, unordered_multimap
    // 哈希表
    //和上面類似,增刪改查的時間復雜度是 O(1)但是不支持 lower_bound()/upper_bound(), 迭代器的++,--

bitset, 圧位
    bitset<10000> s;
    ~, &, |, ^
    >>, <<
    ==, !=
    []
        
count()		返回有多少個1
any()  		判斷是否至少有一個1
none()  	判斷是否全為0
set()  		把所有位置成1
set(k, v)  	將第k位變成v
reset()  	把所有位變成0
flip()  	等價於~
flip(k) 	把第k位取反


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM