【解題報告】Codeforces Round #383 (Div. 2)

題目鏈接


A. Arpa’s hard exam and Mehrdad’s naive cheat(Codeforces 742A)

思路

1378n 看似很可怕,但實際上如果只求其個位數的話,問題就可以轉化成求 8n 的個位數。在紙上用小規模的 n 演算 8n 後發現,其個位數是有規律的。具體是 8,4,2,6 四個數字呈週期性出現。
於是將 nmod4 的方式分類就好了。另外必須小心 13780

代碼

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

int n;

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d", &n);
    if(n == 0) {
        puts("1");
    }
    else if(n % 4 == 1) {
        puts("8");
    }
    else if(n % 4 == 2) {
        puts("4");
    }
    else if(n % 4 == 3) {
        puts("2");
    }
    else {
        puts("6");
    }
    return 0;
}

B. Arpa’s obvious problem and Mehrdad’s terrible solution(Codeforces 742B)

思路

看到題目很自然地想枚舉二元組 (i,j) 去驗證是否有 a[i]a[j]=x 。但顯然 105 這種數據規模是不允許 O(n2) 的算法出現的。
在數據中尋找突破點。我們發現 a[i] 的值的上限是 105 ,這就告訴我們,可以用數組開 105 的空間(這裏用 cnt 數組)來記錄每個數值出現次數。這樣我們只用枚舉 j ,訪問過的 a[j] 都做統計: cnt[a[j]]++ 。對當前 j ,統計 a[i]a[j]=x 的次數等價於統計 a[j]x=a[i] 的個數。這意味着如果想知道 a[i]a[j]=x 的次數,我們只需查詢 cnt[a[j]x] 就可以了。

代碼

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

typedef long long ll;
const int maxn = 1e5 + 10;
int n, x, a[maxn], cnt[maxn<<2];
ll ans;

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d%d", &n, &x);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    for(int i = 1; i <= n; i++) {
        ans += cnt[a[i]^x];
        cnt[a[i]]++;
    }
    printf("%I64d\n", ans);
    return 0;
}

C. Arpa’s loud Owf and Mehrdad’s evil plan(Codeforces 742C)

思路

這題看上去貌似不太容易發現端倪,但是如果注意到 icrushi 的關係的話,就會發現題中的所有的i構成了有向圖(可能是不連通的)。
根據題目給出的信息,有向圖中的每個節點的出度爲 1 (這個很重要,避免了很多奇怪的環圖的出現)。
然後就是處理奇怪的規則:對於任意的 x ,若從 x 出發到達 y 的話,則從 y 出發也要到達 xx 可以等於 y )。直覺告訴我們圖中一定得有環,而且每個點都要在環上。

  • 先考慮只有一個環的情況( n 表示該環上的點的個數, t 表示該環滿足規則需要的最小 t )。當環上有奇數個點時, x=y 必須成立才能滿足規則,也就是說對這個環,只有 t=n 才能滿足規則。當環上有偶數個點時當然可以模仿奇數情況,但是 t=n2 顯然可以得到更小的解(表示 x,y 互爲“在對面的點”)。
  • 其次考慮有多個環的情況(只可能在不同的來連通分量上)。當 t 爲所有環的 t 的倍數時,所有環上的規則仍能運行良好。於是問題就轉化成求所有環的 t 的最小公倍數。
  • 最後考慮有點不出現在環上的情況。對在環上的點計數,若計數結果不等於 n 的話這種情況就發生了(點的出度爲 1 保證了計數不會重複)。

根據以上分析,只要找到圖中所有的環的長度,問題就迎刃而解了。那麼我們可以對圖用類似拓撲排序的搜索(這裏是DFS)的方法找環(將未訪問,訪問過,正在訪問的點分別標記爲 1,1,0 ,當訪問標記爲 0 的點時就說明遇到了環)。對每個節點記錄下該點在搜索樹中的深度,就能在碰到環的時候計算出環的長度了。

代碼

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

const int maxn = 110;
bool fail;
// vis保存了{-1, 0, 1}三種值
int n, a, ans, sum, vis[maxn], dep[maxn];
vector <int> vec, G[maxn];

// 找到所有的環並計算其長度
void dfs(int u, int d) {
    vis[u] = 0;
    dep[u] = d;
    for(int v : G[u]) {
        // 發現環的存在
        if(vis[v] == 0) {
            // 計算長度並保存下來
            vec.push_back(d - dep[v] + 1);
            // 用於判斷是否有點不在環上
            sum += d - dep[v] + 1;
        }
        // 繼續訪問下個節點
        else if(vis[v] < 0) {
            dfs(v, d + 1);
        }
        // 用於判斷是否有點不在環上(保險措施)
        else {
            fail = true;
        }
    }
    vis[u] = 1;
}

// 計算最小公倍數
int lcm(int x, int y) {
    return x / __gcd(x, y) * y;
}

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a);
        G[i].push_back(a);
    }
    // 訪問每個環
    memset(vis, -1, sizeof(vis));
    for(int i = 1; i <= n; i++) {
        if(vis[i] < 0) {
            dfs(i, 1);
        }
    }
    if(fail == true || sum != n) {
        puts("-1");
        return 0;
    }
    ans = 1;
    // 計算最終結果
    for(int i = 0; i < vec.size(); i++) {
        if(vec[i] % 2 == 0) {
            ans = lcm(ans, vec[i] >> 1);
        }
        else {
            ans = lcm(ans, vec[i]);
        }
    }
    printf("%d\n", ans);
    return 0;
}

D. Arpa’s weak amphitheater and Mehrdad’s valuable Hoses(Codeforces 742D)

思路

如果熟悉揹包問題的人可能比較容易反應過來這是個揹包問題的變形。但是即使熟悉揹包問題,也會覺得“小團體”要麼全拿要麼只能拿一個的規則十分棘手。
考慮將有 x 個人的小團體看做一個“聯合實體”,也就是一個人。那麼我們可以將小團體看成一個集合,其中有 x+1 個元素,其中包括 x 個人和我們創造的“聯合實體”。顯然對這個集合,我們要麼不取這個集合中的元素,要麼只取集合中的一個元素。我們可以處理出所有的集合(通過用並查集合並出“聯合實體”)。
處理出這些集合又該怎麼辦呢?熟悉分組揹包的人會發現這是一個分組揹包問題。每個集合是分組揹包中的一個物品組,用一組的物品反覆利用上一組的信息更新 DP 二維表中的一行然後每組新開一行即可。

代碼

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

const int maxn = 3000;
bool vis[maxn];
int n, m, k, f, u, v, cnt, ans;
int b[maxn], w[maxn], B[maxn], W[maxn], p[maxn], d1[maxn], d2[maxn];
vector <int> G[maxn];

// 並查集的初始化
void init() {
    for(int i = 1; i <= n; i++) {
        W[i] = w[i];
        B[i] = b[i];
        p[i] = i;
    }
}

// 並查集的查找
int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}

// 並查集的合併
void Union(int x, int y) {
    x = find(x);
    y = find(y);
    if(x == y) {
        return;
    }
    // 數據的合併
    W[x] += W[y];
    B[x] += B[y];
    //  點編號的合併
    p[y] = x;
}

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &w[i]);
    }
    for(int i = 1; i <= n; i++) {
        scanf("%d", &b[i]);
    }
    // 處理出“聯合實體”
    init();
    while(m--) {
        scanf("%d%d", &u, &v);
        Union(u, v);
    }
    for(int i = 1; i <= n; i++) {
        f = find(i);
        G[f].push_back(i);
    }
    // 將“聯合實體”放入集合中
    cnt = n;
    for(int i = 1; i <= n; i++) {
        f = find(i);
        if(vis[f] == false) {
            G[f].push_back(++cnt);
            w[cnt] = W[f];
            b[cnt] = B[f];
            vis[f] = true;
        }
    }
    // 分組揹包(滾動數組實現)
    memset(d1, 0, sizeof(d1));
    for(int i = 1; i <= n; i++) {
        memset(d2, 0, sizeof(d2));
        for(int u : G[i]) {
            for(int j = k; j >= w[u]; j--) {
                d2[j] = max(d2[j], d1[j-w[u]] + b[u]);
            }
        }
        for(int j = 0; j <= k; j++) {
            d1[j] = max(d1[j], d2[j]);
        }
    }
    // 在DP表中尋找答案
    for(int j = 1; j <= k; j++) {
        ans = max(ans, d1[j]);
    }
    printf("%d\n", ans);
    return 0;
}

(其它題目略)

發佈了70 篇原創文章 · 獲贊 30 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章