1. 程式人生 > 實用技巧 >2020721水題大賽題解

2020721水題大賽題解

A. A(異或粽子)

題目描述

  • 小粽是一個喜歡吃粽子的好孩子。今天她在家裡自己做起了粽子。
  • 小粽面前有 n 種互不相同的粽子餡兒,小粽將它們擺放為了一排,並從左至右編號為 1 到 n 。第 i 種餡兒具有一個非負整數的屬性值 \(a_i\) 。每種餡兒的數量都足夠多,即小粽不會因為缺少原料而做不出想要的粽子。小粽準備用這些餡兒來做出 k 個粽子。
  • 小粽的做法是:選兩個整數 l,r ,滿足 \(1\le l\le r\le n\),將編號在 \([l, r]\) 範圍內的所有餡兒混合做成一個粽子,所得的粽子的美味度為這些粽子的屬性值的異或和。(異或就是我們常說的 xor 運算,即 C/C++ 中的 ^ 運算子或 Pascal 中的 xor 運算子)
  • 小粽想品嚐不同口味的粽子,因此它不希望用同樣的餡兒的集合做出一個以上的粽子。
  • 小粽希望她做出的所有粽子的美味度之和最大。請你幫她求出這個值吧!

輸入格式

  • 從標準輸入讀入資料。
  • 第一行兩個正整數 n,k ,表示餡兒的數量,以及小粽打算做出的粽子的數量。
  • 接下來一行為 n 個非負整數,第 i 個數為 \(a_i\) ,表示第 i 個粽子的屬性值。

輸出格式

  • 輸出到標準輸出。
  • 輸出一行一個整數,表示小粽可以做出的粽子的美味度之和的最大值。

樣例輸入

3 2
1 2 3

樣例輸出

6

樣例說明

  • 小粽可以選取 \([3, 3], [1, 2]\) 兩個區間的餡兒做成粽子,兩個粽子的美味度都為 3,和為 6 。可以證明不存在比這更優的方案。

資料範圍與提示

測試點 \(n\) \(k\)
\(1-8\) \(\le 10^3\) \(\le 10^3\)
\(9-12\) \(\le 5\times 10^5\) \(\le 10^3\)
\(13-16\) \(\le 10^3\) \(\le 2\times 10^5\)
\(17-20\) \(\le 5\times 10^5\) \(\le 2\times 10^5\)
  • 對於所有的輸入資料都滿足:\(1\le n\le 5 \times 10^5, 1\le k\le \min\left \{ \frac{n(n-1)}{2} ,2\times 10^5\right \}, 0\le a_i\le 4294967295\)

Solve

  • 區間異或和可以轉換成對兩端點求解,要求出前k大的數對,把這些數對分成 n+1 類,每一類選出最大的壓入大根堆中,迴圈k次取出堆頂,記錄答案,並將堆頂所屬類別的下一大的數壓入隊中。
  • 查詢第k大的值就是進行二分。
  • 每一類是分別以j結尾的數對,需要在j之前找到一個i使得i~j之間異或和第k大,必須保證 i<j ,這樣就得用到可持久化trie樹,如果不保證 i<j 一個結果就會分別在i類和j重複計算,我們可以直接取前2k大的數,最後答案減半即可。

Code

#include <queue>
#include <cstdio>
#define ll long long
using namespace std;
const int N = 5e5 + 5;
struct Node {
    int x, k;
    ll w;
    Node() {}
    Node(ll a, int b, int c) {
        w = a, x = b, k = c;
    }
    bool operator < (const Node &b) const {
        return w < b.w;
    }//過載運算子,堆頂是w值最大的
};
priority_queue<Node> q;
int t[N*33][2], tot = 1, n, k, w[N*33];//注意trie樹的空間
ll s[N], ans;//本題需要unsigned int 這裡直接開long long
void Add(ll x) {
    int p = 1;
    for (int i = 31; i >= 0; --i) {
        bool y = x>>i & 1;
        if (!t[p][y]) t[p][y] = ++tot;
        p = t[p][y];
        ++w[p];
    }
}
ll Ask(ll x, int k) {
    int p = 1;
    ll s = 0;
    for (int i = 31; i >= 0; --i) {
        bool y = x>>i & 1;
        if (w[t[p][!y]] >= k) y ^= 1, s |= (ll)1 << i;
        else k -= w[t[p][!y]];
        p = t[p][y];
    }
    return s;
}
int main() {
    scanf("%d%d", &n, &k);
    k <<= 1;
    Add(0LL);//需要將s[0]是值加入tire樹
    for (int i = 1; i <= n; ++i) {
        scanf("%lld", &s[i]);
        s[i] ^= s[i-1];//異或字首和
        Add(s[i]);
    }
    for (int i = 0; i <= n; ++i)
        q.push(Node(Ask(s[i], 1), i, 1));
    while (k--) {
        Node a = q.top(); q.pop();
        ans += a.w;
        q.push(Node(Ask(s[a.x], a.k + 1), a.x, a.k + 1));
    }
    printf("%lld\n", ans >> 1);
    return 0;
}

B. B(tree)

題目描述

  • 給你一棵 n 個點的樹,邊長為 1,有 m 次詢問,每次詢問兩個節點 x,y 求從 x 隨機遊走到 y 的期望長度。遊走定義為從當前節點出發等概率隨機走向相連的其他節點。答案\(\mod 1e9+7\)

輸入格式

  • 第一行兩個整數 n,m,接下來 n-1 行每行兩個整數代表樹上的一條邊。
  • 接下來 m 行,每行兩個整數表是詢問的 x,y 。

輸出格式

  • 一共 m 行,每行一個整數表示對應詢問的答案。

樣例輸入

4 2
1 2
2 3
3 4
1 4
3 4

樣例輸出

9
5

資料範圍與提示

  • 對於20%資料 \(n,m\le 10\)
  • 對於40%資料 \(n,m\le 1000\)
  • 另有20%保證樹是一條鏈
  • 對於100%資料 \(n,m\le 10^5\)

Solve

  • 一道期望題,考試時按題目rand模擬,0分,考完加強了一下程式碼,能拿20分呀。
    正解感覺有點像DP
  • 題目中游走定義為從當前節點出發等概率隨機走向相連的其他節點,則從 x 走向每個節點的概率為\(\frac{1}{deg[x]}\)deg[x]為 x 的度,即連線的節點數。
  • f[x]表示從 x 走到 x 的父節點的期望長度
    f[x]就等於直接走到父節點的期望長度+走到每個子節點再走回自己再走父節點的期望長度

寫成表示式
\(f[x]=\frac{1}{deg[x]} + \sum_{y|son of x} \frac{1+f[y]+f[x]}{deg[x]}\)
化簡可得
\(f[x]=deg[x] + \sum_{y|son of x} f[y]\)

  • g[x]表示從 x 的父節點走到 x 的期望長度
    g[x]就等於直接走到x的期望長度+走到父節點的父節點再走回來的期望長度+走到父節點的其他子節點在走回父節點再走到x的期望長度

寫成表示式
\(g[x]=\frac{1}{deg[fa[x]]}+\frac{1+g[x]+g[fa[x]]}{deg[fa[x]]}+\sum_{y|other son of fa[x]}\frac{1+g[x]+f[y]}{deg[fa[x]]}\)
化簡可得
\(g[x]=deg[fa[x]]+g[fa[x]]+\sum_{y|other son of fa[x]}f[y]\)
由f[x]的式子可得
\(g[x]=f[fa[x]]-f[x]+g[fa[x]]\)

  • f[x]和g[x]都求出來了,在進行樹上字首和,x到y的期望長度就是
    \(sumf[x]-sumf[lca]+sumg[y]-sumg[lca]|lca=Lca(x,y)\)

Code

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 1e5 + 5, M = 1e9 + 7;
struct Side {
    int t, next;
}e[N<<1];
int head[N], tot;
void Add(int x, int y) {
    e[++tot] = (Side) {y, head[x]};
    head[x]= tot;
}
int n, m, f[N], g[N], fa[N][21], d[N], dep[N];
void Dfs1(int x) {//預處理出每個節點的深度,父節點,f值
    dep[x] = dep[fa[x][0]] + 1;
    f[x] = d[x];
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].t;
        if (y == fa[x][0]) continue;
        fa[y][0] = x;
        Dfs1(y);
        f[x] = (f[x] + f[y]) % M;
    }
}
void Dfs2(int x) {//預處理出g值
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].t;
        if (y == fa[x][0]) continue;
        g[y] = ((ll)f[x] - f[y] + g[x]) % M;
        Dfs2(y);
    }
}
void Dfs3(int x) {//預處理出f,g的樹上字首和
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].t;
        if (y == fa[x][0]) continue;
        f[y] = (f[x] + f[y]) % M;
        g[y] = (g[x] + g[y]) % M;
        Dfs3(y);
    }
}
int Jump(int x, int k) {
    int a = 0;
    while (k) {
        if (k & 1) x = fa[x][a];
        k >>= 1; ++a;
    }
    return x;
}
int Lca(int x, int y) {//倍增求Lca
    if (dep[x] < dep[y]) swap(x, y);
    x = Jump(x, dep[x] - dep[y]);
    if (x == y) return x;
    for (int i = 20; i >= 0; --i)
        if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; ++i) {
        int x, y;
        scanf("%d%d", &x, &y);
        Add(x, y); Add(y, x);
        ++d[x]; ++d[y];
    }
    Dfs1(1); 
    Dfs2(1); 
    f[1] = 0;//f[1] = g[1] = 0,因為1沒有父節點
    Dfs3(1);
    for (int i = 1; i <= 20; ++i)
        for (int x = 1; x <= n; ++x)
            fa[x][i] = fa[fa[x][i-1]][i-1];//Lca的預處理
    while (m--) {
        int x, y;
        scanf("%d%d", &x, &y);
        int lca = Lca(x, y);
        printf("%lld\n", ((ll)f[x] - f[lca] + g[y] - g[lca]) % M);
    }
    return 0;
}

C. C(四葉草)

題目描述

  • SueJane熱衷於找尋四葉草,並堅信可以帶來好運。
  • SueJane現在有 n 株四葉草,她給這些四葉草按照好看的程度設定了排名。不存在兩株四葉草的排名相同,因此,這個排名序列是一個 1 ~ n 的排列。
  • SueJane現在想把這些四葉草按一定的位置排成一排。設排在第 i 的位置的四葉草排名為 $p_ i $,那麼對於SueJane而言,如果對於 \(\renyi i, 1 i n\),都能找到一個 j,滿足 \(i j n\) 且 $ | p_ i - p _ j|$(即 後面的位置有一株四葉草排名與 i 相差 1),那麼這個序列看起來就比較美觀。比如一個序列 1, 4, 3, 2 是美觀的,而 2, 4, 3, 1 就不美觀(3 後面不存在符合要求的數)。
  • 此外,SueJane還對一些位置做出了限制,內容為規定某位置上的四葉草排名必須為 \(k_i\)
  • 現在她想知道,有多少個序列是滿足條件的。請你求出滿足條件的序列個數 \(\mod 998244353\) ,不然就會被SueJane派去找四葉草。

輸入格式

  • 第一行為兩個整數 n, K,分別表示SueJane一共有 n 株四葉草,並且給出了 K 條限制。
  • 接下來 K 行,每行 2 個數 x,y,表示第 x 個位置上的四葉草排名必須為 y 。

輸出格式

  • 一行一個整數表示答案。

樣例輸入

4 2
1 1
2 4

樣例輸出

2

樣例解釋

  • 兩個限制分別要求 \(p_1 = 1, p _2 = 4\)
  • 符合條件的序列有 \(1,4,2,3\)\(1,4,3,2\),可以證明不存在其它符合條件的排列。

資料範圍與提示

  • 10% 的資料滿足 \(1 n 10\)
  • 20% 的資料滿足 \(1 n 20\)
  • 30% 的資料滿足 \(1 n 500\)
  • 另有 10% 的資料滿足 K=0;
  • 對於 100% 的資料,滿足 \(1 n 5000,0 K 233\),限制條件中不存在同一個位置限制不同的數或不同的位置限制同一個數。

Solve

解法0

辣雞SueJane肯定不會造資料!限制條件都是互相矛盾的!
大力 puts("0");
得分:\(0pts\)
辣雞SueJane造的資料最大的特點就是限制條件沒有矛盾。所有的答案都不是0 。

  • 暴搜可以拿30,打表可以發現 k=0 的情況,答案是\(2^{n-1}\),再騙10分。

  • 根據題意我們可以知道最後一個位置是一個特殊的位置,因為他後面沒有數,也就是沒有限制
    我們考慮n個數(n>3)的前3個數:123,並假設這三個數沒有限制,將4放在最後一位,可以想到這3個數一定是升序,反證一下,如果2在1前面,1後面就沒有絕對值等於1的數(0不可選),運用數學歸納法可以知道不論幾個數,都是升序排序,同理5到n這幾個數就是逆序排序,這樣只要保證每個數的相對順序,前後兩個序列就可以順便排。

  • 定義f[i][j]為前面i個數,後面j個數,此時選到i+j的方案數,轉移方式如下:

    • i+j限制為i,可以放在前面序列,f[i][j] += f[i-1][j]

    • i+j限制為n-j+1,可以放在後面的序列,f[i][j] += f[i][j-1]

    • i+j限制不為以上兩種,不可以轉移

    • i+j沒有限制,f[i][j] += f[i-1][j] + f[i][j-1]|i,j!=0

Code

#include <cstdio>
using namespace std;
const int N = 5e3 + 5, M = 998244353;
int n, k, a[N], f[N][N], ans;
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= k; ++i) {
        int x, y;
        scanf("%d%d", &x, &y);
        a[x] = y;
    }
    f[0][0] = 1;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j + i < n; ++j)
            if (a[i+j]) {
                if (a[i+j] == i) f[i][j] = (f[i][j] + f[i-1][j]) % M;
                else if (a[i+j] == n - j + 1) f[i][j] = (f[i][j] + f[i][j-1]) % M;
            }
            else {
                if (i) f[i][j] = (f[i][j] + f[i-1][j]) % M;
                if (j) f[i][j] = (f[i][j] + f[i][j-1]) % M;
            }
    if (a[n]) return printf("%d\n", f[a[n]-1][n-a[n]]), 0;
    for (int i = 0; i < n; ++i)
        ans = (ans + f[i][n-1-i]) % M;
    printf("%d\n", ans);
    return 0;
}

D. D(Kefa and Watch)

題目描述

  • 給定一個長度為 n 的僅包含 0 - 9 的數字的數字串,要求實現兩個操作
    1.給定 l,r,c,將 \([l,r]\) 上的數字全部改為 c
    2.給定 l,r,c,判斷區間 \([l,r]\)是否有長度為 c 的迴圈節
  • 說明一下題目中迴圈節的定義
    • 假設我們有一個這樣的串: 010101010
      那麼2,4,6,8都是該串的迴圈節(即最後一個迴圈可以是不滿的)

輸入格式

  • 第一行為兩個整數 n, m,n 表示數字串的長度,m 表示操作個數
  • 第二行為長度為 n 的僅包含 0 - 9 的數字串
  • 下面 m 行,每行有四個數字 t,l,r,c, t = 1 時為操作1, t = 2 時為操作2

輸出格式

  • 對於每個 t = 2 的操作,輸出一行YES或NO

樣例輸入1

3 3
112
2 2 3 1
1 1 3 8
2 1 2 1

樣例輸出1

NO
YES

樣例輸入2

6 5
334934
2 2 5 2
1 4 4 3
2 1 6 3
1 2 3 8
2 3 6 1

樣例輸出2

NO
YES
NO

資料範圍與提示

  • 對於30%的資料 \(m\le 5e3\)
  • 對於100%的資料 \(m\le 1e5\)
  • 保證資料有一定梯度

Solve

  • 方法1:線段樹維護Hash值

  • 方法2:直接上庫中的函式memset,memcmp,注意memcmp比較函式的返回結果,如果相同會返回0

Code1

//有簡單的函式就可以解決的方法,何必這樣麻煩,所以省略(其實是沒寫)

Code2

#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2e5 + 5;
int n, m;
char a[N];
int main() {
    scanf("%d%d%s", &n, &m, a + 1);
    while (m--) {
        int od, l, r, c;
        scanf("%d%d%d%d", &od, &l, &r, &c);
        if (od == 1) memset(a + l, c + '0', r - l + 1);
        else puts(memcmp(a + l, a + l + c, r - l + 1 - c) ? "NO" : "YES");
    }
    return 0;
}