1. 程式人生 > >SCOI2016 Day1 簡要題解

SCOI2016 Day1 簡要題解

目錄

「SCOI2016」背單詞

題意

這出題人語文水平真低。。 搬了 skylee 大佬的題意。

給你 \(n\) 個字串,不同的排列有不同的代價,代價按照如下方式計算(字串 \(s\) 的位置為 \(x\) ):

  1. 排在 \(s\) 後面的字串有 \(s\) 的字尾,則代價為 \(n^2\)
  2. 排在 \(s\) 前面的字串有 \(s\) 的字尾,且沒有排在 \(s\) 後面的 \(s\) 的字尾,則代價為 \(x-y\)\(y\) 為最後一個與 \(s\) 不相等的字尾的位置);
  3. \(s\) 沒有後綴,則代價為 \(x\)

\(n \le 10^5, 1 \le \sum_s |s| \le 5.1 \times 10^5\)

題解

首先 \(n^2\) 的代價明顯比其他兩種操作的總和還多,顯然不夠優秀。

我們需要儘量避免 \(1\) 情況的出現,這顯然是可以達到的。

因為所有後綴的出現會存在一個偏序結構,構成了一個 \(DAG\) ,我們找個 \(DFS\)

序遍歷就行了。

其實這個 \(DAG\) 就是它反串構成的 \(Trie\)

我們相當於要在 \(Trie\) 上找一個 \(DFS\) 序把每個 關鍵點 編號,最小化 每個點與它祖先第一個 關鍵點 的編號差值的和。

關鍵點: 就是插入串最後的結束節點。

由於是個 \(DFS\) 序, 對於一個點的每個兒子需要遍歷的話,肯定要遍歷這個兒子的子樹。

所以對於當前這層貪心的話,不難發現這是一個類似於接水問題,我們肯定是讓需要時間較短 (\(Size\) 較小)的兒子排前面。

然後發現直接實現只會有 \(40\) 分。

為什麼錯了呢?

假設對於 \(\overline{ba}, \overline{bc}\)

這兩個串,但是不存 \(\overline{b}\) 串。但由於 \(Trie\) 上會存在 \(\overline{b}\) 這個節點,所以我們會在空串的時候考慮的時候會假設有 \(\overline{b}\) 這個串。

這顯然是不行的。其中一種解決辦法就是,前面我們需要把 \(n\) 個串的字首關係,重新建個新樹。然後再按前面那樣做就可以了。

所以複雜度就是 \(O(n \log n + |S|)\) 的。

程式碼

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define pb push_back

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2012.in", "r", stdin);
    freopen ("2012.out", "w", stdout);
#endif
}

typedef long long ll;

ll ans = 0;

namespace Trie {

    const int Maxn = 510010, Alpha = 26;

    int from[Maxn];

    int Size = 0, sz[Maxn], trans[Maxn][Alpha], key[Maxn];

    inline void Insert(char *str) {
        int u = 0;
        For (i, 1, strlen(str + 1)) {
            int id = str[i] - 'a';
            if (!trans[u][id]) trans[u][id] = ++ Size;
            ++ sz[u = trans[u][id]];
            from[u] = id;
        }
        key[u] = true;
    }

    vector<int> G[Maxn];
    void Get_Tree(int u = 0, int Last = 0) {
        if (key[u]) G[Last].pb(u), Last = u;
        For (i, 0, Alpha - 1) if (trans[u][i])
            Get_Tree(trans[u][i], Last);
    }

    int id = 0;
    void Dfs(int u = 0, int Last = 0) {
        if (u) ans += (++ id) - Last, Last = id;
        sort(G[u].begin(), G[u].end(), [&](const int &lhs, const int &rhs) { return sz[lhs] < sz[rhs]; } );
        for (int v : G[u]) Dfs(v, Last);
    }

};

char str[510010];

int main () {

    File();

    using namespace Trie;

    int n = read();

    For (i, 1, n) {
        scanf ("%s", str + 1);
        reverse(str + 1, str + strlen(str + 1) + 1); Insert(str);
    }
    
    Get_Tree(); Dfs();
    printf ("%lld\n", ans);
    
    return 0;

}

「SCOI2016」幸運數字

題意

有一顆 \(n\) 個點的樹,每個點有個權值 \(G_i\)

\(q\) 次詢問,每次詢問樹上一條路徑上所有數 \(G_i\) 中選擇一些數異或的最大值。

\(n \le 20000, q \le 200000, G_i \le 2^{60}\)

題解

從一個集合中選擇一些數異或出最大值顯然是線性基的模板。

那麼我們只需要得到樹上一條路徑的線性基。

但線性基是不可減的,我們可以考慮用倍增把線性基合併。

複雜度就是 \(O((n + q) \log n \times 60^2)\) 可以卡過去。

其實有更好的解決方式,由於 \(q\) 比較大,我們儘量使它不要帶 \(\log\)

可以點分治,把每個詢問掛在被第一次被分開的中心,然後走到一個詢問的點後把它線性基保留起來。

最後複雜度是 \(O((n \log n + q) \times 60^2)\) 的。

其實也可以樹剖實現,一開始預處理到鏈頂的線性基值,複雜度是 \(O((n + q \log n) \times 60^2)\) 的。

總結

樹上查鏈不可減但可以合併的資訊,有三種方式。不支援修改可以用 倍增(可線上 or 點分治(離線) or 樹剖(可線上)

支援修改就用 樹剖 + 線段樹

注意線性基插入一個線性基的複雜度是 \(\displaystyle O(\frac{L^2}{\omega})\) 的,合併的複雜度是 \(\displaystyle O(\frac{L^3}{\omega})\)

程式碼

這個做法還是比較好寫的。

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define pb push_back

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;}
template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;}

template<typename T>
inline T read() {
    T x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2013.in", "r", stdin);
    freopen ("2013.out", "w", stdout);
#endif
}

typedef long long ll;

template<int Maxn>
struct Linear_Base {

    ll Base[Maxn + 1];

    Linear_Base() { Set(Base, 0); }
    
    inline void Insert(ll val) {
        Fordown (i, Maxn, 0) 
            if (val >> i & 1) {
                if (!Base[i]) { Base[i] = val; break ; }
                else val ^= Base[i];
            }
    }

    inline ll Max() {
        ll res = 0;
        Fordown (i, Maxn, 0)
            chkmax(res, res ^ Base[i]);
        return res;
    }

};

typedef Linear_Base<60> Info;

Info Merge(Info x, Info y) {
    For (i, 0, 60) if (y.Base[i]) x.Insert(y.Base[i]); return x;
}

void Add(Info &x, Info y) {
    For (i, 0, 60) if (y.Base[i]) x.Insert(y.Base[i]);
}

const int N = 20100;

vector<int> G[N];

int n, q, dep[N], anc[N][17], Log2[N]; Info data[N][17]; 

inline ll Calc(int x, int y) {
    Info res = Merge(data[x][0], data[y][0]);
    if (dep[x] < dep[y]) swap(x, y);
    int gap = dep[x] - dep[y];
    Fordown (i, Log2[gap], 0) 
        if (gap >> i & 1)
            Add(res, data[x][i]), x = anc[x][i];

    Add(res, data[x][0]);
    if (x == y)
        return Merge(res, data[y][0]).Max();

    Fordown (i, Log2[dep[x]], 0)
        if (anc[x][i] != anc[y][i]) {
            Add(res, data[x][i]);
            Add(res, data[y][i]);
            x = anc[x][i]; y = anc[y][i];
        }
    Add(res, data[x][0]);
    Add(res, data[y][0]);
    return Merge(res, data[anc[x][0]][0]).Max();
}

void Dfs_Init(int u, int fa = 0) {
    dep[u] = dep[anc[u][0] = fa] + 1;
    for (int v : G[u]) 
        if (v != fa) Dfs_Init(v, u);
}

int main() {

    File();

    n = read<int>(); q = read<int>();

    For (i, 1, n) data[i][0].Insert(read<ll>());
    For (i, 1, n - 1) {
        int u = read<int>(), v = read<int>(); 
        G[u].pb(v); G[v].pb(u);
    }
    Dfs_Init(1);

    For (i, 2, n) Log2[i] = Log2[i >> 1] + 1;
    For (j, 1, Log2[n]) For (i, 1, n) {
        anc[i][j] = anc[anc[i][j - 1]][j - 1];
        data[i][j] = Merge(data[i][j - 1], data[anc[i][j - 1]][j - 1]);
    }

    while (q --)
        printf ("%lld\n", Calc(read<int>(), read<int>()));

    return 0;

}

「SCOI2016」萌萌噠

題意

一個長度為 $ n $ 的大數,用 $ S_1S_2S_3 \ldots S_n $表示,其中 $ S_i $ 表示數的第 $ i $ 位,$ S_1 $ 是數的最高位,告訴你一些限制條件,每個條件表示為四個數 $ (l_1, r_1, l_2, r_2) $,即兩個長度相同的區間,表示子串 $ S_{l_1}S_{l_1 + 1}S_{l_1 + 2} \ldots S_{r_1} $ 與 $ S_{l_2}S_{l_2 + 1}S_{l_2 + 2} \ldots S_{r_2} $ 完全相同。

比如 $ n = 6 $ 時,某限制條件 $ (l_1 = 1, r_1 = 3, l_2 = 4, r_2 = 6) $,那麼 $ 123123 \(、\) 351351 $ 均滿足條件,但是 $ 12012 \(、\) 131141 $ 不滿足條件,前者數的長度不為 $ 6 $,後者第二位與第五位不同。問滿足以上所有條件的數有多少個。

$ 1 \leq n \leq 10 ^ 5, 1 \leq m \leq 10 ^ 5, 1 \leq {l_i}_1, {r_i}_1, {l_i}_2, {r_i}_2 \leq n $ 並且保證 $ {r_i}_1 - {l_i}_1 = {r_i}_2 - {l_i}_2 $。

題解

不難發現其實每次就是限制對應的位置數相同。

然後最後就會限制成 \(tot\) 種可以填的不同的數,最後的答案就是 \(9 \times 10^{tot - 1}\)

我們把強制要求相等的數放入並查集中就行了。直接實現這個過程是 \(O(n^2 \alpha(n))\) 的複雜度。

考慮如何優化,區間對應連邊容易想到線段樹優化建邊。但此處沒有那麼好用。

考慮把一個區間拆成 \(\log\) 個長為 \(2^i\) 的區間。

具體來說令 \(fa[i][j]\)\(i\) 向右長為 \(2^j\) 的區間在並查集上的 \(root\)

初始化的時候 \(fa[i][j]\)\(root\)\(i\)

然後一個詢問我們就在對應區間上連邊就行了。

但是最後我們區間的連通性並不能代表點的連通性,所以我們需要把連通性下放。

具體來說把 \((i, j)\) 這個點和 \((i, j - 1), (i + 2^{j - 1}, j - 1)\) 對應相連就行了。

最後看 \((i, 0)\) 有幾個不同的聯通塊就可以完成了。

其實前面那個那個不需要拆成 \(O(\log n)\) 個區間。由於是要保證聯通,多連是沒有關係的,我們可以類似 \(ST\) 表查詢那樣,把最大的兩個可以代表區間相連。

複雜度就是 \(O((n \log n + m) \alpha (n))\)

總結

這題還是比較巧妙的,類似於線段樹優化建邊的思想,我們可以拆成子區間去解決這個問題。

類似於這種題,只要構建出模型保證和原圖一樣的連通性即可。

程式碼

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2014.in", "r", stdin);
    freopen ("2014.out", "w", stdout);
#endif
}

const int N = 2e5 + 1e3;

int n, m, fa[N][22], Log2[N];

int find(int x, int y) {
    return fa[x][y] == x ? x : fa[x][y] = find(fa[x][y], y);
}

const int Mod = 1e9 + 7;

inline int fpm(int x, int power) {
    int res = 1;
    for (; power; power >>= 1, x = 1ll * x * x % Mod)
        if (power & 1) res = 1ll * res * x % Mod;
    return res;
}

int main () {

    File();

    n = read(); m = read();

    For (i, 2, n) Log2[i] = Log2[i >> 1] + 1;
    For (i, 1, n) For (j, 0, Log2[n]) fa[i][j] = i;

    For (i, 1, m) {
        int p1 = read(), r1 = read(), p2 = read(), r2 = read();
        int len = Log2[r1 - p1 + 1];
        fa[find(p1, len)][len] = find(p2, len);
        fa[find(r1 - (1 << len) + 1, len)][len] = find(r2 - (1 << len) + 1, len);
    }

    Fordown (j, Log2[n], 1) For (i, 1, n) {
        int u = find(i, j), v = find(i, j - 1);
        fa[v][j - 1] = find(u, j - 1);
        u = find(i, j), v = find(i + (1 << j - 1), j - 1);
        fa[v][j - 1] = find(u + (1 << j - 1), j - 1);
    }

    int tot = 0;
    For (i, 1, n) if(find(i, 0) == i) ++ tot;
    printf ("%lld\n", fpm(10, tot - 1) * 9ll % Mod);

    return 0;

}