1. 程式人生 > 其它 >[考試總結]ZROI-21-CSP7連-DAY2 總結

[考試總結]ZROI-21-CSP7連-DAY2 總結

ZROI-21-CSP7連-DAY2 總結

#T1 IP 地址

#題意簡述

一個合法的 IP 地址每位數字最小為 \(0\),最大為 \(255\),且不含前導零。每位數字中間有一個 "." 隔開。

給定一個文字串,其中最多包含 \(4\) 個整數(可能極大),問是否是合法的 IP 地址,如果不合法請更正為合法 IP,數字大於 \(255\) 則修改為 \(255\).

#大體思路

把快讀魔改一下就好了,但是一定要注意細節,比如快讀實際會向後多讀一個字元,可以藉此判斷最後是否有多餘字元。

#Code

const int N = 100010;
const int INF = 0x3fffffff;

int is_not_ip(0), cnt;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1;
    char c = getchar();
    for (; !isdigit(c); c = getchar()) {
        if (c == '.') ++ cnt;
        if (c != '.' || cnt > 3) is_not_ip |= 1;
    }
    for (; ; c = getchar())  {
        if (!isdigit(c)) {
            if (c != '.') is_not_ip |= 1;
            else if (++ cnt > 3) is_not_ip |= 1;
            break;
        }
        if (c == '0' && x == 0) is_not_ip |= 1;
        x = x * 10 + c - '0';
        if (x > 255) x = 255, is_not_ip |= 1;
    }
    x *= f;
}

int a[4];

int main() {
    for (int i = 0; i < 4; ++ i) read(a[i]);
    if (is_not_ip) printf("NO\n");
    else printf("YES\n");
    for (int i = 0; i < 4; ++ i) {
        printf("%d", a[i]);
        if (i < 3) printf(".");
    }
    return 0;
}

#T2 字串

#題意簡述

給定一個長度為 \(n\) 的字串,其中一共包含兩種字元:AP,其中,APPP 可以消去,消去後剩下兩段字串會拼在一起,問經過若干次消除後能得到的最小字串長度為多少。

#大體思路

注意到 A 只能由 AP 消去,而 P 可以由 PP 消去,考慮貪心:當前的 A 要麼在後面被消去,要麼無法消去,而多出來的 P 對答案的貢獻最多是 \(1\),所以優先消掉 A 一定是最優的選擇。

#Code

const int N = 10010;
const int INF = 0x3fffffff;

char s[N];
int n, pcnt, acnt;

int main() {
    scanf("%s", s); n = strlen(s);
    for (int i = 0; i < n; ++ i) {
        if (s[i] == 'A') ++ acnt;
        else acnt ? -- acnt : ++ pcnt;
    }
    printf("%d", acnt + (pcnt & 1));
    return 0;
}

#T3 繼承類

#題意簡述

實在不好概述,這裡直接粘來原題面。

現在發明了一種類似於 C++ 的面向物件程式語言中的類宣告。

每個類宣告的格式為 "\(K : P_1\ P_2\ \dots\ P_K;\)"

其中 \(K\) 是要宣告的新類的名稱,\(P_1\)\(P_2\)\(\dots\)\(P_k\)\(K\) 繼承的類的名稱。

例如,"shape : ;" 是不繼承任何其他類的類 “shape” 的宣告,而 “square : shape rectangle;” 是繼承類 “shape” 和 “rectangle” 的類 “square” 的宣告。

如果類 \(K_1\)

繼承類 \(K_2\) ,類 \(K_2\) 繼承類 \(K_3\) ,依此類推,直到類 \(K_{m−1}\) 繼承類 \(K_m\) ,那麼我們說所有類 \(K_1,K_2,\dots,K_{m−1}\) 派生自類 \(K_m\)

程式語言的規則禁止迴圈定義,因此不允許從自身派生一個類。換句話說,類層次結構形成了一個有向無環圖。此外,不允許在類層次結構中出現所謂的菱形。一個菱形由四個不同的類 \(A\)\(B\)\(X\)\(Y\) 組成,而且它滿足(如下圖所示):

  • \(X\)\(Y\) 派生自 \(A\)
  • \(B\) 派生自 \(X\)\(Y\)
  • \(X\) 不是從 \(Y\) 派生的,類 \(Y\) 也不是從 \(X\) 派生的。

現在你會獲得 \(n(n\le 1000)\) 個要按順序處理的類宣告,並確定每個類宣告是否是正確宣告。

正確宣告的類被新增到層次結構中,而錯誤的類被丟棄。宣告 “\(K : P_1\ P_2\ \dots\ P_K;\)” 如果滿足以下條件,則認為是正確宣告:

  1. \(K\) 尚未宣告。
  2. 所有類別 \(P_1,P_2,\dots,P_k\) 之前已經宣告過。請注意,此條件可確保類永遠不會從其自身派生,或者類層次結構中不能存在迴圈。
  3. 通過新增繼承了 \(P_1,P_2,\dots,P_k\) 的類 \(K\) 以後,類層次結構保持有序,即沒有形成任何菱形。

現在需要你分別處理上述宣告並確定每個宣告的正確性。

#大體思路

直接考慮性質三:簡單分析“菱形”的性質,不難發現不存在菱形的前提是所有的 \(P_i\) 要麼是嚴格的祖孫關係,要麼毫無關係,總之不能為旁系親屬,發現如果當前語句合法,那麼所有的 \(P_i\) 的旁系親屬都是當前 \(K\) 的旁系親屬,很好維護,但是直接用 bool 陣列維護關係的話時間複雜度為 \(O(n^3)\),不能接受,又發現維護的值要麼是 \(0\) 要麼是 \(1\),且支援位運算,直接上 bitset,時間複雜度優化到 \(O(\dfrac{n^3}{w})\),可以艹過去了。

#Code

const int N = 1010;
const int INF = 0x3fffffff;

string s, t;
int n, cnt, tcnt, q[N];

map <string, int> mp;
bitset <1010> f[N], b[N], tmp;

inline bool check() {
    tmp.reset();
    for (int i = 1; i <= tcnt; ++ i)
      tmp[q[i]] = 1;
    for (int i = 1; i <= tcnt; ++ i)
      if ((tmp & b[q[i]]).any()) return false;
    return true;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n; int is_wrong = 0;
    for (int i = 1; i <= n; ++ i) {
        is_wrong = tcnt = 0;
        cin >> s; cin >> t; cin >> t;
        while (t != ";") {
            if (!mp[t]) is_wrong |= 1;
            q[++ tcnt] = mp[t]; cin >> t;
        }
        if (!check()) is_wrong |= 1;
        if (is_wrong || mp[s]) {
            cout << "greska\n"; continue;
        }
        cout << "ok\n"; mp[s] = ++ cnt;
        f[cnt][cnt] = 1;
        for (int j = 1; j <= tcnt; ++ j)
          f[cnt] |= f[q[j]];
        for (int j = 1; j < cnt; ++ j)
          if (!f[cnt][j] && (f[j] & f[cnt]).any()) {
              b[cnt][j] = 1;
          }
    }
    return 0;
}

#T4 子圖

#題意簡述

依舊是不好簡述,上原題面。

Cuber QQ 的研究興趣是在圖 \(G=(V(G),E(G))\) 中找到最好的 \(k\)-degree 子圖。

子圖 \(S\)\(G\)\(k\)-degree 子圖需要滿足以下要求:

  • 每個頂點 \(v(v\in S)\)\(S\) 中至少有 \(k\) 度;
  • \(S\) 是連通的 ;
  • \(S\) 是極大的,即 \(S\) 的任何超圖都不是 \(k\)-degree 子圖,除了 \(S\) 本身。

然後 Cuber QQ 定義子圖 \(S\) 的分數。在定義分數之前,他首先定義:

  • \(n(S)\):子圖 \(S\) 中的頂點數,即 \(n(S)=|V(S)|\)
  • \(m(S)\):子圖 \(S\) 的邊數,即 \(m(S)=|E(S)|\)
  • \(b(S)\):子圖 \(S\) 中的邊界邊數,\(b(S)=|{(u,v)|(u,v)∈E(G),u∈V(S),v∉V(S),v∈V(G)}|\);

他定義一個子圖的分數為 \(score(S)=M\cdot m(S)−N\cdot n(S)+B\cdot b(S)\),其中 \(M,N,B\) 是給定的常數。

子圖的分數越高,Cuber QQ 認為它越好。你需要在圖 \(G\) 中找到最好的 \(k\)-degree 子圖。如果有許多 \(k\)-degree 子圖的分數相同,則應最大化 \(k\)。輸出最大的分數及對應的 \(k\).

#大體思路

首先,如果不考慮連通性問題,那麼 \((k+1)\)-degree 一定是 \(k\)-degree 的子圖,於是我們可以先求得一個標記,如果一個點屬於 \(k\)-degree 而不屬於 \(k+1\)-degree,那麼該點標記為 \(k\),求得這個標記的過程可以通過像剝捲心菜一樣將原圖層層剝開,具體細節我們留到後面去說;

假設我們已經求得了每個點的標記,再來考慮通過將捲心菜原圖一層層還原回去得到每個 \(k\)-degree 的 score;顯然我們是通過加入標記為 \(k\) 的點得到 \(k\)-degree,考慮單個新加入的點,他們對 \(n,m,b\) 的貢獻分別是什麼。

先來引入一些新記號:

  • \(E(u,>)\) 表示 \(u\) 的邊中另一個點標記比 \(u\) 大的邊的集合;
  • \(E(u,<)\) 表示 \(u\) 的邊中另一個點標記比 \(u\) 小的邊的集合;
  • \(E(u,=)\) 表示 \(u\) 的邊中另一個點 \(v\) 標記與 \(u\) 相等但 \(v>u\) 的邊的集合;

現在不難寫出新的貢獻:

  • \(\Delta n=1\)
  • \(\Delta m=|E(u,>)|+|E(u,=)|\)
  • \(\Delta b=|E(u,<)|-|E(u,>)|\)

在合併的過程中,可能會導致幾個連通塊合成一個連通塊,可以採用並查集提前維護出各個連通塊的代表元素,代表元應當是整個連通塊中標記最小的點,這樣才能正確地完成合並。為下面合併答案,同時記錄每個小連通塊合併後的大連通塊的編號,以及當前連通塊的 \(k\).

並查集採用按秩合併+路徑壓縮,總體時間複雜度為 \(O(m+n)\),下面的程式碼僅採用了路徑壓縮,總體時間複雜度為 \(O(m\log n+n)\).

#一些細節實現

獲取標記時,先用桶排將所有點按照度數從小到大排序,我們自小到大遍歷這個序列,對於每個點,將與其相連的所有度數大於該點度數的點的度數減一,同時直接改變其在數列中的位置,將其放到原本度數的開頭位置,對應度數的開頭位置後移一位,這樣維護可以保證在任意時刻佇列中都是有序的。考慮為什麼只有度數大於該點的點需要改變度數:小於該點的意味著已經被遍歷,其度數就是其標記;等於該點的點,顯然應當與該點屬於同一層,直接保留度數作為標記;對於大於的,根據 \(k\)-degree 的定義,如果不在同一層,當前邊貢獻的度數顯然不符合要求,應當減去,如果應當在同一層,那麼由於處理完的度數對應的應當是正確的標記,而當前大於正確標記,應當減去。

詳細見下方程式碼。

#Code

#define ll long long
#define VI vector <int>
#define pii pair <ll, int>

const int N = 2000100;
const int INF = 0x3fffffff;

template <typename T>
inline T Min(const T a, const T b) {return a < b ? a : b;}

template <typename T>
inline T Max(const T a, const T b) {return a > b ? a : b;}

struct Edge {int u, v, nxt;} e[N];

struct UFS {
    int fa[N]; int *d_;

    inline void init(int x, int *d) {
        for (int i = 0; i <= x; ++ i) fa[i] = i;
        this->d_ = d;
    }

    inline int find(int x) {
        while (x != fa[x]) x = fa[x] = fa[fa[x]];
        return x;
    }

    inline void merge(int x, int y) {
        x = find(x), y = find(y);
        if (x != y)  {
            if ((d_[x] > d_[y]) || (d_[x] == d_[y] && x > y))
              swap(y, x);
            fa[y] = x;
        }
    }
} ufs;

int n, m, d[N], head[N], ecnt(1), cbtot;
int frt, tal, q[N], pos[N], bin[N], max_k, cb_id[N];
int inq[N], kpc[N], kcnt; ll A, B, C;

vector <int> tk, pa;

inline void add_edge(int u, int v) {
    e[ecnt].u = u, e[ecnt].v = v; ++ d[u];
    e[ecnt].nxt = head[u], head[u] = ecnt ++;
}

inline void Get_Tags() {
    int max_d = 0, st = 0, now = 0;
    for (int i = 1; i <= n; ++ i)
      ++ bin[d[i]], max_d = Max(max_d, d[i]);
    for (int i = 0; i <= max_d; ++ i)
      now = bin[i], bin[i] = st, st += now;
    for (int i = 1; i <= n; ++ i)
      pos[i] = bin[d[i]] ++, q[pos[i]] = i;
    for (int i = max_d; i; -- i) bin[i] = bin[i - 1];
    bin[0] = 0, bin[max_d + 1] = n;
    for (int i = 0; i < n; ++ i) {
        int v = q[i];
        for (int j = head[v]; j; j = e[j].nxt) {
            int u = e[j].v;
            if (d[u] > d[v]) {
                int du = d[u], pu = pos[u];
                int pw = bin[du], w = q[pw];
                if (u != w) {
                    pos[u] = pw, q[pu] = w;
                    pos[w] = pu, q[pw] = u;
                } ++ bin[du], -- d[u];
            }
        }
    }
    for (int i = 1; i <= n; ++ i)
      max_k = Max(max_k, d[i]);
}

inline void Get_Connected_Blocks() {
    ufs.init(n, d); pa.push_back(0); tk.push_back(0);
    for (int k = max_k; k; -- k) {
        int l = bin[k], r = bin[k + 1] - 1; kcnt = 0;
        for (int i = l; i <= r; ++ i) {
            int v = q[i]; 
            for (int j = head[v]; j; j = e[j].nxt){
                int u = e[j].v;
                int fp = ufs.find(u);
                if (d[fp] > k) if (!inq[fp])
                  kpc[++ kcnt] = fp, inq[fp] = true;
                if (d[fp] >= k) ufs.merge(v, fp);
            }
        }
        for (int i = l; i <= r; ++ i) {
            int v = q[i], fp = ufs.find(v);
            if (!cb_id[fp]) {
                cb_id[fp] = ++ cbtot;
                pa.push_back(-1);
                tk.push_back(d[fp]);
            }
            cb_id[v] = cb_id[fp];
        }
        for (int i = 1; i <= kcnt; ++ i) {
            int v = kpc[i], fp = ufs.find(v);
            pa[cb_id[v]] = cb_id[fp],inq[v] = false;
        }
    }
}

void Compute_the_Answer() {
    VI vert(cbtot + 1, 0), edge(cbtot + 1, 0), boun(cbtot + 1, 0);
    for (int i = 1; i <= n; ++ i) {
        int ci = d[i], lt = 0, eq = 0, gt = 0;
        for (int j = head[i]; j; j = e[j].nxt) {
            int nbr = e[j].v, cnbr = d[nbr];
            if (cnbr < ci) ++ lt;
            else if (cnbr == ci) {
                if (i < nbr) ++ eq;
            } else ++ gt;
        }
        int ti = cb_id[i]; ++ vert[ti];
        edge[ti] += gt + eq; boun[ti] += lt - gt;
    }
    pii ans(1ll * (-1e18), -1);
    for (int i = 1; i <= cbtot; ++ i) {
        int f = pa[i];
        if (f != -1)
          vert[f] += vert[i], edge[f] += edge[i], boun[f] += boun[i];
        ll score = A * edge[i] - B * vert[i] + C * boun[i];
        if (tk[i] > 0) ans = max(ans, {score, tk[i]});
    }
    printf("%d %lld\n", ans.second, ans.first);
}

int main() {
    scanf("%d%d%lld%lld%lld", &n, &m, &A, &B, &C);
    for (int i = 1; i <= m; ++ i) {
        int u, v; scanf("%d%d", &u, &v);
        add_edge(u, v); add_edge(v, u);
    }
    Get_Tags(); Get_Connected_Blocks();
    Compute_the_Answer(); return 0;
}