【解題報告】Codeforces Round #405 (rated, Div. 2, based on VK Cup 2017 Round 1)

題目鏈接


A. Bear and Big Brother(Codeforces 791A)

思路

由於 220320 都會超過 106 ,所以只要模擬 20 年兩人的體重變化就可以了。當然也可以列方程解之,不過太麻煩。

代碼

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

typedef long long ll;
ll a, b, x, y;

int main() {
//    freopen("Input3.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> a >> b;
    x = 1;
    y = 1;
    for(int i = 1; i <= 20; i++) {
        x *= 3;
        y *= 2;
        if(a * x > b * y) {
            cout << i << endl;
            break;
        }
    }
    return 0;
}

B. Bear and Friendship Condition(Codeforces 791B)

思路

這種有“關係”的題目,用圖來建模最合適不過。所以問題轉化爲每個連通分量的子圖是否構成“完全圖”,也就是兩點之間是否都有邊相連。完全圖有一個性質:其點數 n 與邊數 m 滿足 m=n×(n1)2 。利用這個性質我們可以用 DFS 的方式(或者 BFS )訪問每個連通分量,在每個分量內統計點數和邊數。就可以判斷該圖是否合理了。在算乘法的時候注意防溢出。

代碼

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

typedef long long ll;
const int maxn = 2e5;
vector <int> G[maxn];
bool vis[maxn];
int u, v, n, m;
ll V, E;

void dfs(int u) {
    vis[u] = true;
    E += G[u].size();
    V++;
    for(int v : G[u]) {
        if(vis[v] == true) {
            continue;
        }
        dfs(v);
    }
}

int main() {
//    freopen("Input1.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;
    while(m--) {
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= n; i++) {
        if(vis[i] == true) {
            continue;
        }
        V = E = 0;
        dfs(i);
        if(V * (V - 1) != E) {
            puts("NO");
            return 0;
        }
    }
    puts("YES");
    return 0;
}

C. Bear and Different Names(Codeforces 791C)

思路

本題只需要構造一組解。搜索的話複雜度太高,可以先考慮貪心的思想。首先應當明確,輸入中的一個字符串 YESNO 描述的是一個窗口的情況,也就是窗口中的名字是否有重複。
例如 k=4 的某個窗口 [1,1,2,3,4] 是有重複的, k=5 的某個窗口 [3,4,1,5,2] 是沒有重複的。注意,這裏名字的描述方式是數字,答案要求輸出真實人名,我們只需要將數字映射到人名上就行了
如果窗口的描述是 YES 的話那麼爲了簡化問題,構造一個升序序列就行了,例如 [1,2,3,4] ,如果窗口的描述是 NO 的話那麼爲了簡化問題,讓窗口首尾的元素相同其它元素保持升序就行了,例如 [1,2,3,1] 。爲什麼要這麼做呢?原因是要爲以下的貪心算法做鋪墊,下面提出貪心算法:

(注意,下面的構造方法以第一組樣例爲例)

  1. 先構造前 k1 個元素。將其構造成升序即可。那麼我們構造出序列 [1,2]
  2. 然後構造第 k 個元素。檢查第 1 個字符串,如果其爲 NO 則在序列末尾添加與其窗口起始元素相同的元素,如果其爲 YES ,那麼添加新的元素即可。在第一組樣例中此時的序列變爲 [1,2,1]
  3. 重複第 2 個步驟,即不斷往序列末尾添加新元素。序列依次被構造成 -> 1,[2,1,2] -> 1,2,[1,2,3] -> 1,2,1,[2,3,4] -> 1,2,1,2,[3,4,5] -> 1,2,1,2,3,[4,5,4]
  4. 最後序列被構造出來了,第一組樣例構造出的序列是 1,2,1,2,3,4,5,4 ,將數字映射到姓名即可。比如可以將數字看成 26 進制數然後用英文字幕 A,B,C …表示 1,2,3 …(如下)。

代碼

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

const int maxn = 100;
string s[maxn];
int name[maxn];
int n, k, cnt;

int main() {
//    freopen("Input1.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> k;
    for(int i = k; i <= n; i++) {
        cin >> s[i];
    }
    for(int i = 1; i <= k - 1; i++) {
        name[i] = ++cnt;
    }
    for(int i = k; i <= n; i++) {
        if(s[i] == "YES") {
            name[i] = ++cnt;
        }
        else {
            name[i] = name[i - k + 1];
        }
    }
    for(int i = 1; i <= n; i++) {
        cout << (char)(name[i] % 10 + 'A');
        cout << (char)(name[i] / 10 + 'a') << ' ';
    }
    return 0;
}

D. Bear and Tree Jumps(Codeforces 791D)

思路

先將問題簡化爲 k=1 的情況。定義 dis(u,v)u,v 兩點之間的距離(假設任意樹邊的長度爲 1 )。那麼答案就是

res(1)=dis(u,v) (for all u,v that u<v)

w[u] 爲以 u 爲根的子樹中含有的節點數。那麼對於每條樹邊 (u,v) 我們可以算其對答案 res 的貢獻度 w[u]×(nw[u]) 。那麼在用 DFS 統計出 w[] 後就可以求出答案

res(1)=(w[u]×(nw[u])) (for all u in the tree)

接着要將問題推廣到 k=2,3,4,5 的情況。可不可以直接算 res(1)k 呢?如果這麼算的話會發現答案比真正的答案變小了。原因是在按照最多跳k步的規則計算答案時,算的是

ans(k)=dis(u,v)k(for all u,v that u < v)

也就是說

ans(k)=res(1)k+δd=res(1)+Δdk

如果能求出 Δd 的話就能求出結果。

那麼我們需要對每條路徑算模 k 的餘數。當然還是不能一條一條路徑算,還是得用貢獻度的方法算。設以 u 爲根節點的子樹爲 subtree(u) 。顯然我們能夠仿照之前求 w[] 數組的方式求 cnt[u][r] ,其表示從 subtree(u) 出發到根節點,有多少條路徑的長度模 kr 。然後 subtree(u)Δd 的貢獻度是可以計算的(這個貢獻度代表以 u 爲最近公共祖先所有點對之間的距離對 Δd 的貢獻度),設其爲 del(subtree(u)) ,那麼

Δd=del(subtree(u)) (for all u in the tree)

代碼

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

typedef long long ll;
const int maxn = 2e5 + 5, maxk = 10;
vector <int> G[maxn];
int n, k, u, v;
ll ans, w[maxn], cnt[maxn][maxk];

int sub(int a, int b) {
    return ((a - b) % k + k) % k;
}

// 邊搜索邊統計需要的數據
void dfs(int u, int p, int d) {
    w[u] = cnt[u][d % k] = 1;
    for(int v : G[u]) {
        if(v == p) {
            continue;
        }
        dfs(v, u, d + 1);
        for(int i = 0; i < k; i++) {
            for(int j = 0; j < k; j++) {
                // r爲餘數,k爲還要多少能模k餘0
                int r = sub(i + j, 2 * d);
                int need = sub(k, r);
                // 計算貢獻度
                ans += need * cnt[u][i] * cnt[v][j];
            }
        }
        // 從子樹總累加cnt[u][i]
        for(int i = 0; i < k; i++) {
            cnt[u][i] += cnt[v][i];
        }
        w[u] += w[v];
    }
    // 計算貢獻度
    ans += w[u] * (n - w[u]);
}

int main() {
//    freopen("Input2.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> k;
    for(int i = 1; i <= n - 1; i++) {
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, -1, 0);
    cout << ans / k << endl;
    return 0;
}

(其它題目略)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章