《演算法競賽進階指南》藍書重做記錄

B發表於2021-04-15

重新複習藍書(共 \(323\) 題),爭取 \(1.5\) 個月內完成

此次記錄,過往的題也會重新編寫題解並收錄

補題連結:Here

0x00 基本演算法

位運算

A題:a^b

https://ac.nowcoder.com/acm/contest/996/A

題目描述
求 a 的 b 次方對 p 取模的值,其中 0 <= a,b,p <= 10^9

輸入描述:
三個用空格隔開的整數a,b和p。

輸出描述:
一個整數,表示a^b mod p的值。

例項
輸入: 2 3 9
輸出: 8

思路
這道題是要先算出a的b次冪再對其結果進行求模(取餘),因為b最大可為1e+9,按普通做法來做時間複雜度就太大了,顯然這樣過不了題,
能快速算a的b次冪,就能減小時間複雜度,快速冪就是一種不錯的方法。

什麼是快速冪
快速冪是一種簡化運算底數的n次冪的演算法,理論上其時間複雜度為 O(log₂N),而一般的樸素演算法則需要O(N)的時間複雜度。簡單來說快速冪其實就是抽取了指數中的2的n次冪,將其轉換為時間複雜度為O(1)的二進位制移位運算,所以相應地,時間複雜度降低為O(log₂N)。

程式碼原理
\(a^{13}\) 為例,
先把指數13化為二進位制就是1101,把二進位制數字1101直觀地表現為十進位制則是如下的等式:

\[13 = 1 * (2^3) + 1 * (2^2) + 0 * (2^ 1) + 1 * (2^0) \]

這樣一來 \(a^{13}\) 可以如下算出:

\[a^{13} = a ^ {(2^3)} * a ^ {(2^2)} * a ^ {(2^0)} \]

完整AC程式碼如下

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;//將long long型別取個別名:ll型別,為了方便

int power(int a, int b, int mod) {
	ll ans = 1 % mod;
	for (; b; b >>= 1) {
		if (b & 1) ans = ans * a % mod;
		a = (ll)a * a % mod;//顯式轉化為ll型別進行高精度計算,再隱式轉化為int
	}
	return ans;
}

int main() {
	//freopen("in.txt", "r", stdin);
	ios::sync_with_stdio(false), cin.tie(0);
	int a, b, mod;
	cin >> a >> b >> mod;
	cout << power(a, b, mod) << endl;
}

B題:Raising Modulo Numbers

與上面A題寫法一樣

typedef long long ll;
int _;
// 稍微優化下上方程式碼:update 21/01/28
ll qpow(ll a, ll b, ll mod) {
    ll ans = 1;
    a %= mod;
    for (; b; a = a * a % mod, b >>= 1)
        if (b & 1) ans = ans * a % mod;
    return ans;
}
int main() {
    // freopen("in.txt", "r", stdin);
    ios_base::sync_with_stdio(false), cin.tie(0);
    ll M, N;
    for (cin >> _; _--;) {
        cin >> M >> N;
        ll a, b, ans = 0;
        while (N--) {
            cin >> a >> b;
            ans = (ans + qpow(a, b, M)) % M;
        }
        cout << ans << endl;
    }
}

C題:64位整數乘法

連結:https://ac.nowcoder.com/acm/contest/996/C

思路:

類似快速冪的思想,把整數b用二進位制表示,即

\[b = c_{k - 1}2^{k - 1} + c_{k -2}2^{k - 2} + ... + c_02^0 \]

typedef long long ll;
int main() {
	//freopen("in.txt", "r", stdin);
	ios::sync_with_stdio(false), cin.tie(0);
	ll a, b, p; cin >> a >> b >> p;
	ll ans = 0;
	for (; b; b >>= 1) {
		if (b & 1)ans = (ans + a) % p;
		a = (a << 1) % p;
	}
	cout << ans << endl;
}

⭐D題:最短Hamilton路徑

連結:https://ac.nowcoder.com/acm/contest/996/D

解題思路

《演算法競賽進階指南》藍書重做記錄 image-20200807130325034

AC程式碼:

#define ms(a,b) memset(a,b,sizeof a)
int e[21][21], b[1 << 21][21], n;
int main() {
	//freopen("in.txt", "r", stdin);
	ios::sync_with_stdio(false), cin.tie(0);
	cin >> n;
	for (int i = 0; i < n; ++i)
		for (int j = 0; j < n; ++j)
			cin >> e[i][j];
	ms(b, 0x3f); b[1][0] = 0;
	for (int i = 0; i < 1 << n; ++i)
		for (int j = 0; j < n; ++j) if (i >> j & 1)
			for (int k = 0; k < n; ++k) if (~(i >> k) & 1)//if ((i ^ 1 << j) >> k & 1)
				b[i + (1 << k)][k] = min(b[i + (1 << k)][k], b[i][j] + e[j][k]);
	cout << b[(1 << n) - 1][n - 1] << endl;
}

⭐例題:[NOI2014]起床困難綜合徵

題意:

連結:[NOI2014] 起床困難綜合症

貪心從高位到低位列舉,檢驗當前位在初始值為\(0\) 情況下的答案是否可以為\(1\) ,如果不能則檢驗當前位初始值能否為 \(1\),並檢驗當前位在初始值為 \(1\) 情況下的答案是否可以為 \(1\)

int n, m, x;
string str;
pair<string, int> a[100005];
int work(int bit, int now) {  // 用參加的第 bit 位進行n次運算
    for (int i = 1; i <= n; ++i) {
        int x = a[i].second >> bit & 1;
        if (a[i].first == "AND")
            now &= x;
        else if (a[i].first == "OR")
            now |= x;
        else
            now ^= x;
    }
    return now;
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> str >> x;
        a[i] = make_pair(str, x);
    }
    int val = 0, ans = 0;
    for (int bit = 29; bit >= 0; bit--) {
        int res0 = work(bit, 0), res1 = work(bit, 1);
        if (val += (1 << bit) <= m && res0 < res1)
            val += (1 << bit), ans += (res1 << bit);
        else
            ans += (res0 << bit);
    }
    cout << ans << "\n";
    return 0;
}

遞推與遞迴

遞迴實現指數型列舉

int _, n, m, k, x, y;
vector<int> vec;

void calc(int x) {
    if (x == n + 1) {
        for (int i = 0; i < vec.size(); ++i) cout << vec[i] << " ";
        cout << "\n";  // 注意一下,以後輸出回車用 "\n" 而不是 endl
        return;
    }
    calc(x + 1), vec.push_back(x);
    calc(x + 1), vec.pop_back();
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    calc(1);
}

遞迴實現組合型列舉

int n, m;
vector<int> vec;
void calc(int x) {
    // 剪枝,如果已經選取了超過m個數,
    // 或者即使選上剩下所有數也不夠m個就要提前結束搜尋了 ↓
    if (vec.size() > m || vec.size() + (n - x + 1) < m) return;
    if (x == n + 1) {
        for (int i = 0; i < vec.size(); ++i) cout << vec[i] << " ";
        cout << "\n";  // 注意一下,以後輸出回車用 "\n" 而不是 endl
        return;
    }
    calc(x + 1), vec.push_back(x);
    calc(x + 1), vec.pop_back();
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    calc(1);
}

遞迴實現排列型列舉

int n, m;
int order[20];
bool chosen[20];
void cal(int k) {
	if (k == n + 1) {
		for(int i = 1;i<=n;++i)
			cout << order[i] << " ";
		cout << endl; return;
	}
	for (int i = 1;i <= n; ++i) {
		if (chosen[i])continue;
		chosen[i] = true;
		order[k] = i;
		cal(k + 1);
		chosen[i] = false;
	}
}
int main() {
	ios::sync_with_stdio(false), cin.tie(0);
	cin >> n;cal(1);
}

費解的開關

const int N = 6;//因為後續操作讀取的是字串
 
char g[N][N];
char backup[N][N];//備份     --- 用於記錄每次列舉第1行的情況
int n;
int dx[5] = {-1,0,1,0,0}, dy[5] = {0,0,0,-1,1};//用於表示當前位置及該位置的上下左右位置的偏移量
 
//改變當前燈及其上下左右燈的狀況
void turn(int x, int y){
    for(int i = 0; i < 5; i ++){
        int a = x + dx[i], b = y + dy[i];//用於表示當前位置或該位置的上下左右位置
        if(a >= 0 && a < 5 || b >= 0 && b < 5){
            g[a][b] ^= 1;//用於'0' 和'1'的相互轉換     -----根據二者的Ascll碼值特點
        }
    }
}
 
int main(){
    cin >> n;
    while(n --){
        for(int i = 0; i < 5; i ++) cin >> g[i];//讀取資料
 
        int res = 10;//用於記錄操作的結果
        for(int op = 0; op < 32; op ++){//列舉第1行燈的狀態 ---- 也可以採用遞迴實現指數型列舉
            int step = 0;//用於記錄當前情況的操作步數
            memcpy(backup, g, sizeof g);//備份原陣列資料  ----  因為每次列舉都是一種全新的情況
 
            //列舉第一行,若燈滅,則點亮
            for(int j = 0; j < 5; j ++){
                if(!(op >> j & 1)){//也可以是 if(op >> j & 1) ,因為二者情況數量相同
                    step ++;
                    turn(0, j);//翻轉當前燈的狀況
                }
            }
 
            //從第一行向下遞推至倒數第二行
            for(int i = 0; i < 4; i ++){
                for(int j = 0; j < 5; j ++){
                    if(g[i][j] == '0'){//當前行當前位置燈滅
                        step ++;
                        turn(i + 1, j);//改變當前行的下一行該列燈的狀況,使當前行燈亮
                    }
                }
            }
 
            //檢驗最後一行燈是否全亮,若存在暗燈,則此方案不成立
            bool dark = false;
            for(int j = 0; j < 5; j ++){
                if(g[4][j] == '0'){
                    dark = true;
                    break;
                }
            }
 
            if(!dark) res = min(step, res);
            memcpy(g, backup, sizeof backup);//還原資料,用於下次方案的操作
        }
 
        if(res > 6) res = -1;
        cout << res << endl;
    }
    return 0;
}
// 另一種解
int _, a[6], ans, aa[6];
string s;
void dj(int x, int y) {
    aa[x] ^= (1 << y);
    if (x != 1) aa[x - 1] ^= (1 << y);
    if (x != 5) aa[x + 1] ^= (1 << y);
    if (y != 0) aa[x] ^= (1 << (y - 1));
    if (y != 4) aa[x] ^= (1 << (y + 1));
}
void pd(int p) {
    int k = 0;
    memcpy(aa, a, sizeof(a));
    for (int i = 0; i < 5; ++i)
        if (!((p >> i) & 1)) {
            dj(1, i);
            if (++k >= ans) return;
        }
    for (int x = 1; x < 5; ++x)
        for (int y = 0; y < 5; ++y)
            if (!((aa[x] >> y) & 1)) {
                dj(x + 1, y);
                if (++k >= ans) return;
            }
    if (aa[5] == 31) ans = k;
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    for (cin >> _; _--;) {
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= 5; ++i) {
            cin >> s; // 字串讀入更便利處理
            for (int j = 1; j <= 5; ++j) a[i] = a[i] * 2 + (s[j - 1] - '0');
        }
        ans = 7;
        for (int p = 0; p < (1 << 5); p++) pd(p);
        cout << (ans == 7 ? -1 : ans) << "\n";
    }
    return 0;
}

Strange Towers of Hanoi

#define Fi(i, a, b) for (int i = a; i <= b; ++i)
int d[13], f[13];
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    Fi(i, 1, 12) d[i] = d[i - 1] * 2 + 1;
    memset(f, 0x3f, sizeof f), f[1] = 1;
    Fi(i, 2, 12) Fi(j, 1, i) f[i] = min(f[i], 2 * f[j] + d[i - j]);
    Fi(i, 1, 12) cout << f[i] << "\n";
    return 0;
}

Sumdiv (AcWing 97. 約數之和)(數論)(分治)

image-20210129210845354

const int p = 9901;
int pow(int x, int y) {
    int ret = 1;
    for (; y; y >>= 1) {
        if (y & 1) ret = 1ll * ret * x % p;
        x = (ll)x * x % p;
    }
    return ret;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int a, b, ans = 1;
    cin >> a >> b;
    if (!a) return !puts("0");
    for (int i = 2, num; i * i <= a; i++) {
        num = 0;
        while (a % i == 0) a /= i, num++;
        if (num)
            ans =
                ans * (pow(i, num * b + 1) - 1 + p) % p * pow(i - 1, p - 2) % p;
    }
    if (a > 1) ans = ans * (pow(a, b + 1) - 1 + p) % p * pow(a - 1, p - 2) % p;
    cout << ans << "\n";
    return 0;
}

Fractal Streets

題解來源:Click Here

題意:
   給你一個原始的分形圖,t組資料,對於每組資料,輸入3個數n,h,o (n為在第n級,h,o為兩個房子的編號),求在第n級情況下,編號為h和o的兩個點之間的距離*10為多少。
  其中,第n級分形圖形成規則如下:

  1. 首先先在右下角和右上角複製一遍n-1情況下的分形圖
  2. 然後將n-1情況下的分形圖順時針旋轉90度,放到左上角
  3. 最後將n-1情況下的分形圖逆時針旋轉90度 ,放到左下角
    編號是從左上角那個點開始計1,沿著道路計數。

這是著名的通過一定規律無限包含自身的分形圖。為了計算方便,我們將題目中房屋編號從0開始編號,那麼S與D也都減掉1.
大體思路:設calc(n,m)求編號為m的房屋編號在n級城市中的座標位置,那麼距離是:calc(n,s-1) 與 calc(n,d-1)的距離。
從n(n > 1)級城市由四座n-1級城市組成,其中:
  1.左上的n-1級城市由城市結構順時針旋轉90度,從編號的順序看,該結構還做水平翻轉,座標轉換至n級時如下圖。
  2與3.右上和右下和原始城市結構一樣,座標轉換至n級時如下圖。

市由城市結構逆時針旋轉90度,從編號的順序看,該結構也做了水平翻轉。
 
  旋轉座標的變化可通過公式:

 (設len = 2(n-1))當旋轉角度是逆時針90度時,也就是順時針270度時,(x,y)->(y, -x),然後再進行水平翻轉,(y,-x)->(-y,-x)。然後再將圖形平移到n級圖形的左下角,在格子上的座標變化是,水平方向增加len - 1個位置,垂直方向增加2len - 1個位置。因此座標(x,y)按照規則轉移到了(2len-1-y,len-1-x).
  注意:n-1級格子裡擁有的房子數量是cnt = 22n /4,即22n-2.
    當前編號m在N級格子的哪個方位是:m / cnt.
    當前編號m在n-1級格子裡的編號是: m %cnt;
詳細程式碼如下:

using ll = long long;
pair<ll, ll> calc(ll n, ll m) {
    if (n == 0) return make_pair(0, 0);  //邊界
    ll len = 1ll << (n - 1), cnt = 1ll << (2 * n - 2);
    pair<ll, ll> zb = calc(n - 1, m % cnt);
    ll x = zb.first, y = zb.second;
    ll z = m / cnt;
    switch (z) {
        case 0: return make_pair(y, x); break;
        case 1: return make_pair(x, y + len); break;
        case 2: return make_pair(x + len, y + len); break;
        case 3: return make_pair(2 * len - y - 1, len - x - 1); break;
    }
}
int main() {
    int t;
    cin >> t;
    while (t--) {
        ll n, s, d;
        cin >> n >> s >> d;
        pair<ll, ll> zb;
        pair<ll, ll> bz;
        double ans = 0;
        zb = calc(n, s - 1);  //記得-1 QWQ
        bz = calc(n, d - 1);
        ll x, y;
        x = (zb.first - bz.first), y = (zb.second - bz.second);  //邊長居然是10
        ans = sqrt(x * x + y * y) * 10;  //喜聞樂見 勾股定理
        printf("%.0f\n", ans);           //四捨五入
    }
    return 0;
}

非遞迴實現組合型列舉

#include <iostream>
#include <vector>
using namespace std;
vector<int> chosen;
int n, m;
void dfs(int x);
int main() {
    cin >> n >> m;
    dfs(1);
}
void dfs(int x) {
    if (chosen.size() > m || chosen.size() + (n - x + 1) < m) return;
    if (x == n + 1) {
        // if(chosen.size() == 0) return;
        for (int i = 0; i < chosen.size(); i++) printf("%d ", chosen[i]);
        puts("");
        return;
    }
    chosen.push_back(x);
    dfs(x + 1);
    chosen.pop_back();
    dfs(x + 1);
    return;
}

字首和與差分

A題:HNOI2003]鐳射炸彈

按照藍書上的教程做即可,注意這道題卡空間用int 而不是 long long

int g[5010][5010];
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int N, R;
    cin >> N >> R;
    int xx = R, yy = R;
    for (int i = 1; i <= N; ++i) {
        int x, y, w;
        cin >> x >> y >> w, ++x, ++y;
        g[x][y] = w, xx = max(xx, x), yy = max(y, yy);
    }
    for (int i = 1; i <= xx; ++i)
        for (int j = 1; j <= yy; ++j)
            g[i][j] = g[i - 1][j] + g[i][j - 1] - g[i - 1][j - 1] +
                      g[i][j];  //求字首和
    int ans = 0;
    for (int i = R; i <= xx; ++i)
        for (int j = R; j <= yy; ++j)
            //用提前算好的字首和減去其他部分再補上多剪的那部分
            ans =
                max(ans, g[i][j] - g[i - R][j] - g[i][j - R] + g[i - R][j - R]);
    cout << ans << "\n";
    return 0;
}

B題:IncDec Sequence

設 a 的差分序列為 b.

則對區間 [l, r] 的數都加 1,就相當於 b[l]++, b[r + 1]--.

操作分為 4 種.

① 2 ≤ l ≤ r ≤ n (區間修改)

② 1 == l ≤ r ≤ n(修改字首)

③ 2 ≤ l ≤ r == n + 1 (修改字尾)

④ 1 == l ≤ r == n + 1 (全修改)

其中操作 ④ 顯然無用.

操作 ① 價效比最高.

於是可得出方案:先用操作 ① ,使得只剩下 正數 或 負數 ,剩下的用操作 ② 或 ③ 來湊.

using ll = long long;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int n;
    cin >> n;
    vector<ll> a(n + 1, 0), b(n + 2);
    for (int i = 1; i <= n; ++i) cin >> a[i], b[i] = a[i] - a[i - 1];
    ll p = 0, q = 0;
    for (int i = 2; i <= n; ++i) {  // 2~n的正負數和統計
        if (b[i] > 0) p += b[i];
        else if (b[i] < 0) q -= b[i];
    }
    cout << max(p, q) << "\n" << llabs(p - q) + 1 << "\n";
    return 0;
}

C題:Tallest Cow

差分陣列,對於給出第一個區間a,b,他們之間的人肯定比他們矮,最少矮1,那麼就在a+1位置-1,b位置加1,計算字首和,a+1以及之後的都被-1了,b及以後的不變。

重複的區間,不重複計算。

另一種思路:先將所有的牛的高度都設為最大值 然後在輸入一組數A B時 將A B之間的牛的高度都減一。

map<pair<int, int>, bool> vis;
int c[10010], d[10010];
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int n, p, h, m;
    cin >> n >> p >> h >> m;
    while (m--) {
        int a, b;
        cin >> a >> b;
        if (a > b) swap(a, b);
        if (vis[make_pair(a, b)]) continue; // 避免重複計算
        vis[{a, b}] = true, d[a + 1]--, d[b]++;
    }
    for (int i = 1; i <= n; ++i) {
        c[i] = c[i - 1] + d[i];
        cout << h + c[i] << "\n";
    }
    return 0;
}

二分

⭐二分A題:Best Cow Fences

二分答案,判定是否存在一個長度不小於L的子段,平均數不小於二分的值。如果把數列中的每個數都減去二分的值,就轉換為判定“是否存在一個長度不小於L的子段,子段和非負”。

先分別考慮兩種情況的解法(1、子段和最大【無長度限制】,2、子段和最大,子段長度不小於L)

<==>求一個子段,使得它的和最大,且子段的長度不小於L。

子段和可以轉換為字首和相減的形式,即設\(sumj\)表示\(Ai 到 Aj\)的和,

則有:\(max{A[j+1]+A[j+2].......A[i] } ( i-j>=L ) \\ = max{ sum[i] - min{ sum[j] }(0<=j<=i-L) }(L<=i<=n)\)

仔細觀察上面的式子可以發現,隨著i的增長,j的取值範圍 0~i-L 每次只會增大1。換言之,每次只會有一個新的取值進入 \(min\{sum_j\}\) 的候選集合,所以我們沒必要每次迴圈列舉j,只需要用一個變數記錄當前的最小值,每次與新的取值 sum[i-L] 取min 就可以了。

double a[100001], b[100001], sum[100001];
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int n, L;
    cin >> n >> L;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    double eps = 1e-5;
    double l = -1e6, r = 1e6;
    while (r - l > eps) {
        double mid = (l + r) / 2;
        for (int i = 1; i <= n; ++i) b[i] = a[i] - mid;
        for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + b[i];
        double ans = -1e10;
        double min_val = 1e10;
        for (int i = L; i <= n; ++i) {
            min_val = min(min_val, sum[i - L]);
            ans = max(ans, sum[i] - min_val);
        }
        if (ans >= 0)
            l = mid;
        else
            r = mid;
    }
    cout << int(r * 1000) << "\n";
    return 0;
}

排序

A題: Cinema

經典離散化例題,把電影的語言與字幕和觀眾懂的語言放進一個陣列,然後離散化。

最後統計快樂人數。

const int N = 200006;
int n, m, a[N], x[N], y[N], cinema[N * 3], tot = 0, k, ans[N * 3];

int find(int f) { return lower_bound(cinema + 1, cinema + k + 1, f) - cinema; }

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i], cinema[++tot] = a[i];
    cin >> m;
    for (int i = 1; i <= m; ++i) cin >> x[i], cinema[++tot] = x[i];
    for (int i = 1; i <= m; ++i) cin >> y[i], cinema[++tot] = y[i];

    sort(cinema + 1, cinema + tot + 1);
    k = unique(cinema + 1, cinema + tot + 1) - (cinema + 1);
    memset(ans, 0, sizeof(ans));
    for (int i = 1; i <= n; i++) ans[find(a[i])]++;
    int ans0 = 1, ans1 = 0, ans2 = 0;
    for (int i = 1; i <= m; i++) {
        int ansx = ans[find(x[i])], ansy = ans[find(y[i])];
        if (ansx > ans1 || (ansx == ans1 && ansy > ans2)) {
            ans0 = i;
            ans1 = ansx;
            ans2 = ansy;
        }
    }
    cout << ans0 << endl;
    return 0;
}

當然不用離散化也可以做。

簡單使用 unordered_map 對映個數即可

const int N = 2e5 + 10;
int _, n, x, y, tmp, a[N];
unordered_map<int, int> mp;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    for (cin >> _; _--;) cin >> tmp, mp[tmp]++;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= n; ++i) {
        int t;
        cin >> t;
        if (mp[a[i]] > x)
            tmp = i, x = mp[a[i]], y = mp[t];
        else if (mp[a[i]] == x && mp[t] > y)
            tmp = i, y = mp[t];
    }
    cout << tmp << "\n";
    return 0;
}

B題:貨倉選址

排序一下,利用中位數的性質

int n, ans, sum;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    vector<int> v(n);
    for (auto &t : v) cin >> t;
    sort(v.begin(), v.end());
    ans = v[n / 2];
    for (auto &t : v) sum += abs(t - ans);
    cout << sum << "\n";
    return 0;
}

C題:⭐七夕祭

這裡借下 洛凌璃dalao的blog題解

題解

這個題其實是兩個問題:

1.讓攤點上下移動,使得每行的攤點一樣多

2.左右移動攤點,使得每列的攤點一樣多

兩個問題是等價的,就討論第一個

r[i]表示每行的攤點數

然後使得r[i]的一些攤點移動到r[i - 1]和r[i + 1], 類似於"均攤紙牌"

均攤紙牌

有M個人排一排,每個人分別有C[1]~C[M]張拍,每一步中,一個人可以將自己的一張手牌給相鄰的人,求至少需要幾步

顯然, 紙牌總數T能被M整除有解,在有解的情況下, 考慮第一個人:

1.C[1] >= T/M, 第一個人要給第二個人C[1] - T/M張牌
2.C[1] < T/M, 第二個給第一個人T/M - C[1]張牌
本質就是使得第一人滿足要求要|T/M - C[1]|步
那麼滿足第二人就要 |T/M - (C[2] - (T/M - C[1]))| = |2 * T/M - (C[1] + C[2])|步
滿足第三人 |T/M - (C[3] - (T/M - (C[2] - (T/M - C[1]))))| = |3 * T/M - (C[1] + C[2] + C[3])|

到這裡就可以發現,有一段是字首和, 但再仔細化簡以下可以發現

|3 * T/M - (C[1] + C[2] + C[3])|
=|(T/M - C[1]) + (T/M - C[2]) + (T/M - C[3])|
=|(C[1] - T/M) + (C[2] - T/M) + (C[3] - T/M)|

我們可以讓A[i] = C[i] - T/M, S[i]為A[i]的字首和,

那麼對於"均攤紙牌"這道題的答案就是

∑ni=1∑i=1n|S[i]|

對於本題來說,無非是變成了環形問題

直接無腦dp就可以

我們隨便選取一個人k最為斷環的最後一名(即第一個人變為為k + 1),

則從這個人開始的持有的牌數(這行的攤點數), 字首和為

A[k + 1]   S[k + 1] - S[k]
A[k + 2]   S[k + 2] - S[k]
...
A[M]       S[M] - S[k]
A[1]       S[M] - S[k] + S[1]
A[2]       S[M] - S[k] + S[2]
...
A[k]       S[M] - S[k] + S[k]

我們發現S[M] = 0, 所以答案為

|S[k + 1] - S[k]| + ... + |S[M] - S[k]| + |s[M] - S[k] + S[1]| + ... + |S[M] - S[K] + S[k]|

=|S[k + 1] - S[k]| + ... + |S[M] - S[k]| + |-S[k] + S[1]| + ... + |-S[k] + S[k]|

=∑ni=1∑i=1n |S[i] - S[k]|

答案已經很明顯了,像不像"倉貨選址"?

倉貨選址

一條軸上有N家店,每家店的座標為D[1]~D[N],選擇一家點為倉庫向其他商店發貨,求選哪家店,運送距離最短

不就是∑ni=1∑i=1n |D[i] - D[k]| 為答案嗎?

當然是選中位數了啦,

設k左邊有P家店,右邊有Q家店

如果P<Q,那必然將k右移, ans - Q + P,答案明顯變小了

Q>P,同理,故選擇中位數

所以本題的答案就已經近在眼前了, 字首和,求中位數

using ll = long long;
const int maxn = 1e5 + 5;
int n, m, k;
int c[maxn], r[maxn], s[maxn];

ll work(int a[], int n) {
    for (int i = 1; i <= n; ++i) s[i] = s[i - 1] + a[i] - k / n;
    sort(s + 1, s + 1 + n);
    ll ans = 0;
    for (int i = 1; i <= n; ++i) ans += abs(s[i] - s[(n >> 1) + 1]);
    return ans;
}

int main() {
    cin >> n >> m >> k;
    for (int i = 1; i <= k; ++i) {
        int a, b;
        cin >> a >> b, ++c[b], ++r[a];
    }
    if (k % n + k % m == 0)
        cout << "both " << work(c, m) + work(r, n);
    else if (k % n == 0)
        cout << "row " << work(r, n);
    else if (k % m == 0)
        cout << "column " << work(c, m);
    else
        cout << "impossible";
    return 0;
}

相關文章