Codeforces Round #673 (Div. 2)[A-E]

Codeforces Round #673 (Div. 2)[A-E]

A. Copy-paste

題目大意

給定一個長爲 \(n\) 的數組 \(a\) ,你能夠對任意 $ i, j ;(i\neq j)$ ,進行操做 \(a_j=a_i+a_j\)。同時,須要知足數組中任意一項值不超過 \(k\),求最多可進行多少次操做。node

*800 greedy算法

思路分析

這題,顯然是一個離線貪心。不瞭解的能夠查看個人貪心算法專題、數組

排序以後,把最小值不斷賦值給其餘數直到沒法繼續賦值。併發

對於這種題,毫無疑問貪心算法或者數學法會是更好的選擇。ide

代碼

const int maxn = 1e5 + 50;
int f[maxn], mn, pos;
void solve(){
    int n = read(), k = read();
    for (int i = 0; i < n; ++ i) f[i] = read();

    sort(f, f + n);
    LL sum(0);
    for (int i = 1; i < n; ++ i) sum += (k - f[i]) / f[0];
    cout << sum << '\n';
}

B. Two Arrays

題目大意

給定一個長爲 \(n\) 的非負整數序列 \(a\) ,和一個定值 \(T\)spa

定義 \(f(b)\) 爲:指針

序列 \(b\) 中,知足 \(b_i + b_j = T\) 的點對 \((i, j) \; i < j\) 的總數。code

如今,要求對序列 \(a\) 進行黑白染色,使其分爲兩個子序列 \(c, d\) 。求問使得 \(f(c) + f(d)\) 最小的染色方案。排序

*1100get

思路分析

本題具備兩個思路,分別爲 基於數學推演的狀況討論法基於貪心算法的暴力比較法

假如在競勝過程中,若是能大體保證貪心算法的正確性時,用貪心算法顯然更好一些。

這裏主要介紹貪心算法,數學推演其實是將小於 \(\dfrac{T}{2}\) 的分爲一組, 大於 \(\dfrac{T}{2}\) 的分爲一組。等於 \(\dfrac{T}{2}\) 的在兩子序列中週期擺動安放。

而貪心算法則是維護兩個子序列的 hashmap 。當要插入一個數字時,比較其在兩個子序列中分別能組成多少組合法點對,貪心選擇數量較少的進行插入,知足要求。

在看到題面的時候,經過注意到 \(b_i + b_j = T\) ,咱們應該快速聯想到 hashmap 解決此類問題。

在看到最小時,應該快速思考使用在線 or 離線貪心算法。

代碼

const int maxn = 1e5 + 50;
int f[maxn], ans[maxn];
void solve(){
    int n = read(), T = read();
    for (int i = 0; i < n; ++ i) f[i] = read();

    unordered_map<int, int> mp1, mp2;
    mp1.clear(), mp2.clear();

    VI ans(n);
    for (int i = 0; i < n; ++ i){
        if (mp1[T - f[i]] <= mp2[T - f[i]]){
            ans[i] = 0;
            ++ mp1[f[i]];
        }else {
            ans[i] = 1;
            ++ mp2[f[i]];
        }
    }
    show(ans);
}

C. k-Amazing Numbers

題目大意

給定一個長爲 \(n\) 的序列 \(a\),該序列元素大小均在 \([1, n]\) 中。定義 k驚喜數字

  • 在序列 \(a\) 全部長爲 \(k\) 的連續子序列中均出現過的最小元素,若不存在則賦值爲 \(-1\)

請你計算並輸出 \(k \in [1, n]\) 的全部 k驚喜數字

1 <= n <= 3e5

*1500

思路分析

剛拿到這題時,我沒有一點思路。一直在思考是否是滑動窗口,以及如何複用其中的信息,均發現沒法快速解決。

後面轉換角度從每一個元素的特性出發,插入頭尾兩個虛擬結點,尋找各元素之間的最大間隔。並發現這個方法可行。

最終得出以下結論:

  • 假如你不自覺的想入一個問題且在必定時間內不能得出答案時,說明你有一個細節或者角度不對。
  • 若是不自覺的用宏觀解決問題時(好比在段,區間),專一於微觀多是這個題目的解決方案(位,各元素之間)

之因此寫上面這段話,是由於這題當時險些沒有作出,並且不屬於方法不清楚的層面,而是我在思考問題時出現了角度的誤差。謹以此警示。

迴歸這個題目,如上文所提,咱們維護每一個元素的最大間隔。易知,當 \(k\) 大於某元素最大間隔時,就可能對k驚喜數字產生貢獻。在利用 std::map 維護每元組的間隔時,因爲其自動排序的特性,能夠從頭到尾進行處理。

代碼

const int maxn = 3e5 + 50;
int f[maxn];
void solve(){
    int n = read(), tot = 0, cnt(0);
    map<int, int> dis; // 最大間距
    unordered_map<int, int> pre; // 上一個元素出現位置
    for (int i = 1; i <= n; ++ i){ // index from 1 (in order to insert 0-index node)
        f[i] = read();
        if (pre[f[i]] == 0){
            pre[f[i]] = i;
            dis[f[i]] = i;
        }else {
            int dlt = i - pre[f[i]]; // 間距
            // wprint(dlt, dis[f[i]]);
            pre[f[i]] = i;
            if (dlt > dis[f[i]]) dis[f[i]] = dlt;
        }
    }
    for (auto &e: pre){
        int dlt = n + 1 - e.second; // insert (n+1)-index node 
        if (dlt > dis[e.first]) dis[e.first] = dlt;
        // wprint(dlt, e.first, e.second);
    }

    vector<int> ans(n + 1, -1);
    int stop = n + 1;
    for (auto &e: dis){ // 利用 map 的排序特性,從頭到尾處理便可
        if (e.second >= stop) continue;
        for (int i = e.second; i < stop; ++ i) ans[i] = e.first;
        stop = e.second;
    }
    for (int i = 1; i <= n; ++ i) cout << ans[i] << (i == n ? '\n' : ' ');
}

D. Make Them Equal

題目大意

  • 出一個序列 \(a\),求出一個長度不超過 \(3n\) 的操做序列,每次操做以後序列中全部元素必須爲非負整數,操做完成後使序列 \(a\) 中每一個元素相等。
  • 定義一次操做爲:選出 \((i,j,x)\) 三元組,知足 \(i,j\) 爲序列合法下標,\(x\)\(10^9\) 之內非負整數,令 \(a_i:= a_i-x\cdot i,a_j:=a_j+x\cdot i\)
  • 輸出時先輸出操做次數 \(k\),而後輸出 \(k\) 行操做序列。

*2000

思路分析

*2000 分的構造題,實際上比完賽以後想一想仍是很套路的。之後有機會總結一個 你只能用最多 \(c \cdot n\) 次操做,完成一個目標

在這裏進行一個簡單的總結。

用 c * n 次操做(詢問)完成目標

首先,無論可用操做(詢問)數爲多少,一個永遠能夠嘗試的思路是: 以某個最特殊的元組作跳板

  • \(c == 1\),則爲線性操做相似冒泡的感受,從頭到尾線性操做,每次保留其中之一在下一次進行操做。
  • \(c == 2\),通常來講:
    • 可能爲 \(c == 1\)時的狀況,可是每次操做須要兩步C. Chocolate Bunny 相似這題。
    • 或者對每一個元素用一次操做將其轉移到跳板上,再進行一次操做完成目的。
  • \(c==3\),則想法與上述差很少。

總而言之,須要發現操做的特定性質,如互相操做跳板操做

對於這題,比較困難的地方在於每次操做以後保證全部的元素非負。根據上述結論顯然咱們須要選擇一共「跳板

」,毫無疑問這個跳板選擇初始位置 \(1\) 最爲合適。所以,咱們大體制定好了構造策略:

  1. 首先將其餘位置的元素轉移到「跳板上」。
  2. 在藉助跳板分配元素,使得每一個元素相等。

根據題設限制,每一個元素最多轉移 \(a_i - (a_i) \%i\) ,對於餘下的 \(a_i \% i\) ,咱們能夠先補充上 \(i - (a_i \% i)\),再進行轉移。通過思考,這個方案是知足條件的,實際上他也符合一種貪心的思想。咱們儘量避免對於元素進行較大的減操做

所以最終策略爲:

  • 首先,將元素轉移到跳板上:
    • 先將元素補齊至 \((a_i + \Delta) \% == 0\)
    • 將元素轉移到跳板上
  • 經過跳板逐步分配元素。

代碼

const int maxn = 1e4 + 50;
int f[maxn];
void solve(){
    int tot(0), n = read();
    for (int i = 1; i <= n; ++ i) f[i] = read(), tot += f[i];

    if (tot % n != 0){ cout << "-1\n"; return; } // 不能整除顯然不行

    int eve = tot / n;
    vector< tuple<int, int, int> > res;
    for (int i = 2; i <= n; ++ i){
        if (f[i] % i == 0) res.pb({i, 1, f[i] / i});
        else {
            res.pb({1, i, i - (f[i] % i)});
            res.pb({i, 1, (f[i] / i) + 1});
        }
    }
    for (int i = 2; i <= n; ++ i) res.pb({1, i, eve});
    
    wprint(sz(res));
    for (auto &e: res) wprint(get<0>(e), get<1>(e), get<2>(e));
}

E. XOR Inverse

題目大意

給定長度爲 \(n\) \((1\le n\le3\times 10^5)\) 的數列 \(\{a_n\}(0\le a_n\le 10^9)\),請求出最小的整數 \(x\) 使 \(\{a_n\oplus x\}\)逆序對數最少,其中$ \oplus$ 是異或。

*2000 divide and conquer CDQ分治

思路分析

本題最開始會比較容易想到經過 dp 去解決。考慮到逆序對這個經典的問題,便考慮到分治的思路。實際上 bit 的 0 or 1 能夠類比爲二叉樹的左右孩子。若越靠近根結點表明的 bit 越高,則右孩子必定大於左孩子(用了字典樹的知識)。

所以,題目便比較簡單了:運用 CDQ 分治的思路:

首先,咱們維護一個 dp 數組,dp[bit][i]:= x 的第 bit 位爲 i 時逆序對的個數

由於,兩個元素比較大小等價於比較二者第一個不一樣位。不一樣位爲 1 的更大。所以在解題的過程當中,維護兩個數組,左邊表明當前分治到左邊的元素(bit is 0),右邊表明當前分治到右邊的元素(bit is 1) 。所以,右邊必定大於左邊,在利用求解逆序對的知識,經過雙指針處理兩個子樹合併出現的逆序對個數。

  • 若不修改該 bit (等價於 x 的第 bit 位爲 0 ),則逆序對爲直接求得的。
  • 若修改該bit (等價於 x 的第 bit 位爲 1 ),則逆序對數爲總數-不修改時的逆序對個數

以後,貪心選取其中每 bit 逆序對較小的構造答案。

代碼

#define pb push_back
using VI = vector<int>;
using LL = long long;

// CDQ 分治處理點對問題
int dp[35][2];
void helper(VI cur, int bit = 30){
    if (bit < 0 or cur.empty()) return;

    int cnt1(0), ans1(0);
    int cnt2(0), ans2(0);
    VI right, left;

    for (auto &x: cur){
        if ((x >> bit) & 1){
            ans1 += cnt2; // 因爲 index 從大到小,所以 cnt2 表明 index 大於當前元素且元素值小於當前元素的點對數。
            ++ cnt1;
            right.pb(x);
        }else {
            ans2 += cnt1;
            ++ cnt2;
            left.pb(x);
        }
    }
    helper(left, bit - 1), helper(right, bit - 1);
    dp[bit][0] += ans1;
    dp[bit][1] += ans2;
} 

void solve(){
    int n = read();
    VI a(n);
    for (auto &&e: a) e = read();
    reverse(all(a));
    helper(a);
    LL ans(0), res(0);
    for (int i = 0; i <= 30; ++ i){
        ans += min(dp[i][0], dp[i][1]);
        if (dp[i][1] < dp[i][0]) res |= (1 << i);
    }
    cout << ans << ' ' << res << '\n';
}
相關文章
相關標籤/搜索