Codeforces Round #662 (Div. 2)

Codeforces Round #662 (Div. 2)

A. Rainbow Dash, Fluttershy and Chess Coloring

題目大意

給定一個 \(n \times n\) 的正方形區域,要求用兩種顏色爲其上色,且相鄰的塊顏色不能相同。每次上色能對任意塊進行上色,但要求被上色的塊的鄰邊是邊界或是一個已經被上色的塊。至少須要多少回合才能將正方形區域上色成功。node

1 <= n <= 1e9c++

greedy math *800算法

思路分析

這題看 n 的數量級就猜想是一個規律題。數組

如何尋找規律呢?顯然咱們每次上色都是貪心的上儘量多的塊。app

因此,咱們第一次上色會將邊界區域上色一半,第二次上色會完成邊界區域的上色,並把已經上色的區域做爲新的邊界。spa

如圖所示這是 n = 5 的狀況下上色兩次後的圖例。在通過兩次上色後,咱們把已經上色的部分做爲新的邊界,所以咱們從 n = 5 轉移到了 n = 3。從圖片上能夠獲得,兩次操做後,咱們從n = 5 轉移爲 n = 3 以後再進行了一次操做的結果。設計

n = 1 須要操做 1 次, n = 2 須要操做 2 次。且由 \(f(n) = f(n - 2) + 2 - 1 = f(n - 2) + 1\)3d

\[f(1) = 1\\ f(2) = 2\\ f(3) = f(1) + 1 = 2\\ f(4) = f(2) + 1 = 3\\ f(5) = f(3) + 1 = 3 \]

能夠獲得 :code

\[ans = f(n) = \lfloor \frac{n}{2}\rfloor + 1 \]

代碼

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

int main(){
    int t; cin >> t;
    while (t--){
        int n; cin >> n;
        cout << (n / 2) + 1 << '\n';
    }
    return 0;
}

B. Applejack and Storages

題目大意

給你一批木材,每一個木材的長度用 \(a_i\) 表示,你須要用這批木材完成兩個任務:blog

  • 搭建一個正方形(須要 4 個同樣長度的木材)
  • 搭建一個矩形 (須要 2 組 2 個長度同樣的木材,注意能夠是 4 個同樣長的)

每次會進行木材存儲的合法更新,判斷可否同時完成這兩個任務。

1 <= ai <= 1e5

greedy implementation *1400

思路分析

這題不少人用傳統的暴力算法,實際上這題想清楚思路很是簡單。

注意,咱們須要關注的只有:

  • 當前 4 個同樣長的木材的組數 \(four\)
  • 除去 \(four\),以外 2 個同樣長的木材的組數 \(two\)

爲了便於理解舉個例子:假如長爲 1 的木材數量共有 7 個,則 \(four = 1, two = 1\)

那麼能完成任務的條件就是:

  1. \(four \ge 2\)
  2. \(four == 1 \and two \ge 2\)

所以,咱們目前只剩下如何獲得 \(four, two\) 的值這個問題了。

下面經過 3 給例子徹底闡明 \(four, two\) 值的處理,設長爲 \(x\) 的木材共有 \(c\) 個,改變量爲 \(dlt\)

  • \(dlt = 1\)
    • 此時 \(c = c + 1\), 若此時 \(c \% 4 == 0\)\(c\) 恰能整除 4 。這說明以前 \(c = 4 * k + 3\) ,其 \(four = k, two = 1\)。修改以後 $ c = 4 * (k + 1)$, \(four = k + 1, two = 0\)。 也就是 four += 1, two -= 1
    • 若此時 \(c \% 4 != 0 \and c \% 2 == 0\),這說明以前 \(c = 4 * k + 1\),其\(four = k, two = 0\)。修改以後,$ c = 4*k + 2$, \(four = k, two = 1\)。只須要更新 \(two\),也就是 two += 1
    • 若此時上述兩種狀況都不知足,則 \(c\) 原來必定是 \(c = 4*k \quad or \quad c = 4*k + 2\),這時候不須要對 \(four, two\) 進行修改。
  • \(dlt = -1\) ,一樣的道理
    • 假如更新前 \(c = 8\),也就是剛好被 4 整除的狀況。那麼會減小一個 \(four\),增長一個 \(two\)
    • 假如更新前 \(c = 6\),也就是不被 4 整除,但被 2 整除的狀況。那麼只會減小一個 \(two\),不影響\(four\)
    • 不然不須要更新 \(four, two\) 的值

所以算法流程就很清晰了:

  1. 預處理初始的木材,保存每一個木材長度 ai的數量 ci,並計算 four,two
  2. 更新木材數量和four, two,判斷是否知足條件。

代碼

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

const int maxn = 1e5 + 50;
int m[maxn]; // 記錄每一個木材長度有多少個

int main(){
    int n; cin >> n;
    memset(m, 0, sizeof(m));
    int four = 0, two = 0;
    for (int i = 0; i < n; ++ i){
        int ci; cin >> ci;
        ++ m[ci];
        if (m[ci] % 4 == 0) -- two, ++ four; // 狀況一
        else if (m[ci] % 2 == 0) ++ two; // 狀況二
    }

    int q; cin >> q;
    for (int i = 0; i < q; ++ i){
        int dlt;
        char sign;
        cin >> sign >> dlt;
        // cout << four << " "  << two << endl;
        if (sign == '+'){
            ++ m[dlt];
            if (m[dlt] % 4 == 0) -- two, ++ four; // 狀況一
            else if (m[dlt] % 2 == 0) ++ two; // 狀況二
            if (four >= 2 || (four == 1 && two >= 2)) cout << "YES\n";
            else cout << "NO\n";
        }else {
            int save = m[dlt];
            -- m[dlt];
            if (save % 4 == 0) -- four, ++ two;
            else if (save % 2 == 0) -- two;
            if (four >= 2 || (four == 1 && two >= 2)) cout << "YES\n";
            else cout << "NO\n";
        }
    }

    return 0;
}

C. Pinkie Pie Eats Patty-cakes

題目大意

給定 \(n\) 個數 \(a_i\),能夠將全部數任意排列,求出相同的數之間的最小距離的最大值

1 <= n <=1e5

1 <= ai <= n

constructive algorithms greedy math sortings *1700

思路分析

這個題目,最開始想成模板二分題,繞了半天,後面發現之因此不能模板二分在於它不能簡單的貪心放置,必須有策略的放。

那顯然是一個貪心算法,關於貪心算法我作過總結,這個明顯是一個離線貪心算法,也就是預處理完再利用設置好的貪心策略處理。

怎麼才能讓相同元素之間放更多元素呢?舉個例子

首先咱們有 4 個 1,咱們這樣放置1 1 1 1

顯然,咱們須要在 1 之間的空隙進行補全,假設咱們有 3 個 2: 1 2 1 2 1 2 1 這時候,距離爲 2

再而後呢? 假設有 2 個 3:1 2 3 1 2 3 1 2 1,這時候距離仍是爲 2

也就是,咱們必須在間隔中滿滿放置一輪,才能讓答案增長 1,假如沒放置滿則不算。

大家可能會問我,假如 4 個 1 以後出現了 5 個 2 呢?

那就排序呀,保證從大到小就行了

那假如出現了 4 個 1 以後有 4 個 2 呢?

你會發現,效果與 3 個 2 同樣

因此,貪心策略爲:

  1. 咱們首先預處理每一個數字的數量,並對其按照數量從大到小排序
  2. 設最大數量mxlen爲基準,往其中mxlen - 1個間隔進行插入
  3. 若插滿一輪,則答案加一

代碼

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int MOD = 1e9 + 7;
using pii = pair<int, int>;
const int maxn = 1e5 + 50;
int n, m[maxn];
vector<int> a;

struct node{
    int name, cnt, pos; // 歷史遺留,不須要考慮 pos
    node(): name(-1), cnt(-1), pos(-1) {}
    node(int _name, int _cnt): name(_name), cnt(_cnt), pos(-1) {}
};

void solve(){
    cin >> n;
    memset(m, 0, sizeof(m));
    a.clear();
    for (int i = 0; i < n; ++ i){
        int ci; cin >> ci;
        if (m[ci] == 0) a.push_back(ci); // 去重,將出現過的數字提取
        ++ m[ci];
    }

    vector<node> arr;
    arr.clear();

    for (int i = 0; i < a.size(); ++ i) arr.push_back({a[i], m[a[i]]});
    sort(arr.begin(), arr.end(), [&](const node &a, const node &b){
        return a.cnt > b.cnt;
    });

    int mxlen = -1, res = 0, count = 0;
    for (int i = 0; i < arr.size(); ++ i){
        if (mxlen == -1) { mxlen = arr[i].cnt; res = 0; continue; }
        if (arr[i].cnt == mxlen) ++ res;
        else {
            count += arr[i].cnt;
            res += (count / (mxlen - 1)); // 判斷是否插滿一輪
            count = (count % (mxlen - 1)); // 插滿以後剩餘的
        }
    }
    cout << res << endl;
}

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

D. Rarity and New Dress

題目大意

給定一個 \(n \times m\) 的矩陣,矩陣由多個字母組成。找到矩陣中存在菱形的數量(其中單獨一個也算作一個長度爲 1 的菱形)

1 <= n, m <= 2000

dfs and similar dp implementation shortest paths *2100

思路分析

此題首先比較容易想到利用 動態規劃 進行求解。但重點在於動態規劃的 dp 數組的含義。

其思路來源於: 最大正方形,題目大意是在一個二進制矩陣中尋找一個最大的由 1 組成的正方形。

這題中 dp 數組的含義爲 \(dp(i, j)\) 表明以位置 \((i, j)\) 做爲正方形右下角的最大邊長。爲啥會選取右下角呢!搞清楚這個問題對於解決 當前菱形問題的解決有至關大的幫助。

通常來講,dp 可能是順序 dp ,當你遍歷到位置 \((i, j)\) 時,每每只能從前面已處理完的位置獲取信息。由此咱們能夠獲得以下結論:在進行 dp 數組的設計時,須要保證轉移方程只須要從已經處理完的位置獲取信息。

例如,在本題中,假如 dp 數組設計爲 dp[i][j] := 以 (i, j) 爲中心的菱形最大長度 就會出現狀態轉移方程須要 \((i + 1, j)\) 的信息。而這個位置倒是未處理過的。

所以,本題的 dp 數組含義的設計參考了上述結論, dp[i][j] := 以 (i, j) 做爲低格的最大菱形長度

而狀態轉移方程呢:經過下圖能夠比較清晰的理解:

從圖示能夠發現,一個長爲 3 的菱形,對於其底部 \((i, j)\) ,只須要判斷 \((i - 1, j - 1), (i - 1, j + 1), (i - 2, j)\) 三個位置的最小值就能實現狀態轉移。(還須要判斷 \((i - 1, j)\) 是否與 \((i, j)\) 爲相同字母)

在獲得這個結論後,咱們即可以快速的寫出代碼了:

代碼

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e3 + 50;
using LL = long long;
int dp[maxn][maxn];


int main(){
    int n, m; cin >> n >> m;
    vector<string> mat(2, string(m + 1, '$'));
    for (int i = 0; i < n; ++ i){
        string ss; cin >> ss;
        mat.push_back("$" + ss);
    }

    // for (auto &e: mat) cout << e << '\n';

    int ans = 0;
    for (int i = 2; i <= n + 1; ++ i){
        for (int j = 1; j <= m; ++ j){
            if (mat[i][j] != mat[i - 1][j] || mat[i][j] != mat[i - 1][j - 1] || mat[i][j] != mat[i - 1][j + 1] || mat[i][j] != mat[i - 2][j])
                dp[i][j] = 1;
            else dp[i][j] = min({dp[i - 2][j], dp[i - 1][j - 1], dp[i - 1][j + 1]}) + 1;
            ans += dp[i][j];
        }
    }
    cout << ans << '\n';
    return 0;
}
  • 在之後進行相似這題的 幾何dp 時,首先考慮以當前點做爲幾何圖形的最下最右點,再設計 dp 方程。
相關文章
相關標籤/搜索