給定一個 \(n \times n\) 的正方形區域,要求用兩種顏色爲其上色,且相鄰的塊顏色不能相同。每次上色能對任意塊進行上色,但要求被上色的塊的鄰邊是邊界或是一個已經被上色的塊。至少須要多少回合才能將正方形區域上色成功。node
1 <= n <= 1e9
c++
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
能夠獲得 :code
#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; }
給你一批木材,每一個木材的長度用 \(a_i\) 表示,你須要用這批木材完成兩個任務:blog
- 搭建一個正方形(須要 4 個同樣長度的木材)
- 搭建一個矩形 (須要 2 組 2 個長度同樣的木材,注意能夠是 4 個同樣長的)
每次會進行木材存儲的合法更新,判斷可否同時完成這兩個任務。
1 <= ai <= 1e5
greedy
implementation
*1400
這題不少人用傳統的暴力算法,實際上這題想清楚思路很是簡單。
注意,咱們須要關注的只有:
爲了便於理解舉個例子:假如長爲 1 的木材數量共有 7 個,則 \(four = 1, two = 1\)
那麼能完成任務的條件就是:
所以,咱們目前只剩下如何獲得 \(four, two\) 的值這個問題了。
下面經過 3 給例子徹底闡明 \(four, two\) 值的處理,設長爲 \(x\) 的木材共有 \(c\) 個,改變量爲 \(dlt\) :
four += 1, two -= 1
two += 1
所以算法流程就很清晰了:
ai
的數量 ci
,並計算 four
,two
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; }
給定 \(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 同樣
因此,貪心策略爲:
mxlen
爲基準,往其中mxlen - 1
個間隔進行插入#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; }
給定一個 \(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; }