1. 程式人生 > 其它 >[考試總結]ZROI-21-NOIP衝刺-TEST7 總結

[考試總結]ZROI-21-NOIP衝刺-TEST7 總結

ZROI-21-NOIP衝刺-TEST7 總結

#T1 tournament

Time Limit: 1s Memory Limit: 512MiB

#題意簡述

Zbox 投資了一家電子競技俱樂部, 現在這傢俱樂部需要參加一個盃賽.

這次的杯賽中總共有 \(2^n(n\leq18)\) 支參賽隊伍, 隊伍之間的實力差距很大, 不妨認為一場比賽實力較強的隊伍必然會獲勝.

不妨認為實力最弱的隊伍編號為 \(1\),次弱的編號為 \(2\),…,最強的編號為 \(2^n\).

賽程如下所示:

序列 \(p_1,p_2,\dots,p_{2^n}\) 將在輸入中給出, 每個位置上的數字表示在這個位置上的是編號為這個數字的隊伍.

由於 Zbox 非常有錢, 他可以花錢使得他投資的隊伍和另一隻隊伍交換位置(也可以不交換

).

現在 Zbox 希望他的隊伍能夠贏下儘可能多的比賽, 但他卻忘了將他投資的隊伍編號告訴你, 因此你需要輸入對於每個編號的隊伍, 進行一次交換位置操作後, 至多能在這場盃賽中贏下幾輪.

#大體思路

發現 \(x\) 可以贏 \(i\) 場當且僅當存在一個開始位置為 \(k2^{i}+1(k\in N)\) 的長度為 \(2^i\) 的區間的次大值小於 \(x\),於是我們可以用 st 表維護出是否存在長度為 \(2^i\) 且起始位置為 \(k2^i+1(k\in N)\) 的區間的次大值為 \(x\),然後即可得到是否存在長度為 \(2^i\) 且起始位置為 \(k2^i+1(k\in N)\)

的區間的次大值小於等於 \(x\),然後對於每個數 \(O(1)\) 查詢是否存在合法區間次大值小於等於 \(x-1\) 即可,總體時間複雜度為 \(O(n\cdot2^n)\).

#Code

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

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

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

int n, lmt, a[N], mx[20][N], sec[20][N], vis[20][N], ans[N];

int main() {
    read(n); lmt = 1 << n;
    for (int i = 1; i <= lmt; ++ i) read(a[i]);
    for (int i = 1; i <= lmt; ++ i) mx[0][i] = a[i], sec[0][i] = -INF;
    for (int i = 1; i <= n; ++ i)
      for (int j = 1; j + (1 << i) - 1 <= lmt; j += (1 << i)) {
          if (mx[i - 1][j] < mx[i - 1][j + (1 << i - 1)]) {
              mx[i][j] = mx[i - 1][j + (1 << i - 1)];
              sec[i][j] = Max(sec[i - 1][j + (1 << i - 1)], mx[i - 1][j]);
          } else {
              mx[i][j] = mx[i - 1][j];
              sec[i][j] = Max(sec[i - 1][j], mx[i - 1][j + (1 << i - 1)]);
          }
          vis[i][sec[i][j]] |= 1;
      }
    for (int i = 1; i <= n; ++ i)
      for (int j = 1; j <= lmt; ++ j)
        vis[i][j] |= vis[i][j - 1];
    for (int i = 1; i <= lmt; ++ i)
      for (int j = 1; j <= n; ++ j)
        if (vis[j][a[i] - 1]) ans[a[i]] = j;
    for (int i = 1; i <= lmt; ++ i) printf("%d ", ans[i]);
    return 0;
}

#T2 inversion

Time Limit: 1s Memory Limit: 512MiB

#題意簡述

給定一個 \(n(n\leq3\times 10^5)\) 個點的樹,你可以選定樹上任意點為起點,走出一條長度為 \(k(k\leq3\times10^5)\) 的路徑,這條路徑的貢獻是按順序將這條路徑所經過的點的編號排列得到的序列的逆序對數,問整棵樹的貢獻和。

#大體思路

考慮樹上一條長度為 \(k\) 的路徑會以兩種順序計算兩遍代價,考慮一個長度為 \(k+1\) 序列 \(a_i\) 在正反計算兩遍逆序對數會有哪些性質,不難發現,對於數 \(a_i,a_j\),無序數對 \((a_i,a_j)\) 必然會被計算恰好一次,於是整個序列的貢獻便是 \(\binom{k+1}{2}\)

我們將這個結論放回到樹上,也就是對於一個長度為 \(k\) 的路徑,它具有 \(k+1\) 個點,正反計算兩遍得到的貢獻就是 \(\binom{k+1} 2\),於是我們只需要計算長度為 \(k\) 的路徑的個數即可。

這裡採用長鏈剖分進行計算,時間複雜度為 \(O(n)\).

#Code

#define ll long long

const int N = 500010;
const ll MOD = 1e9 + 7;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

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

int n, head[N], ecnt(1), K;

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

int son[N], mxd[N], *dp[N], buf[N << 3];

void dfs(int x, int fa) {
    for (int i = head[x]; i; i = e[i].nxt) {
        if (e[i].v == fa) continue; dfs(e[i].v, x);
        if (mxd[e[i].v] > mxd[son[x]]) son[x] = e[i].v;
    }
    mxd[x] = mxd[son[x]] + 1;
}

int *p = buf; ll cnt = 0;

void DP(int x, int fa) {
    dp[x] = p++; dp[x][0] = 1;
    if (son[x]) DP(son[x], x);
    if (mxd[x] > K) (cnt += dp[x][K]) %= MOD;
    for (int i = head[x]; i; i = e[i].nxt) {
        if (e[i].v == fa || e[i].v == son[x]) continue;
        DP(e[i].v, x);
        for (int j = max(0, K - mxd[x]); j < mxd[e[i].v] && j < K; ++j)
          (cnt += 1ll * dp[e[i].v][j] * dp[x][K - j - 1]) %= MOD;
        for (int j = 1; j <= mxd[e[i].v]; ++j)
          dp[x][j] = (dp[x][j] + dp[e[i].v][j - 1]) % MOD;
	}
}
int main() {
    read(n), read(K);
    for (int i = 1; i < n; ++ i) {
        int u, v; read(u), read(v);
        add_edge(u, v), add_edge(v, u);
    }
    dfs(1, 0), DP(1, 0);
    printf("%lld", cnt * (1ll * (K + 1) * K / 2 % MOD) % MOD);
    return 0;
}

#T3 tree

Time Limit: 1s Memory Limit: 512 MiB

#題意簡述

給定一個 \(n(n\leq5\times10^5)\) 個點的基環樹,每條邊的價值為斷開這條邊後的樹的直徑,如果斷開後不再連通,那麼這條邊的價值為 \(-1\),要求計算所有邊的價值。

#大體思路

又是一個考場上想出來沒調出來的題QnQ

考慮到樹的直徑共有兩種:一種是不經過環的子樹上的直徑,另一種經過環。

對於第一種,我們可以直接通過樹形 DP 進行計算;而對於第二種,我們考慮斷開環上的每一條邊後複製一遍,設 \(f_i\) 表示斷開 \(i\) 後面的邊得到的最大直徑,按如下 DP 方程進行計算:

\[f_{i}=\max\limits_{i-len<j<k\leq i}\{g_j+g_k+k-j\}, \]

其中 \(g_x\) 表示以 \(x\) 為根的子樹的最大深度,對上面的式子進行變形得到:

\[f_i=\max_{i-len<j<i}\{g_j-j+\max\limits_{j<k\leq i}\{g_k+k\}\}, \]

不難發現上面的式子可以通過同時維護兩個單調佇列進行優化(注意邊界!),整體時間複雜度是 \(O(n)\).

#Code

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

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

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

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

int n, head[N], ecnt(2), vis[N], endpos, pre[N], spc[N], nxt[N];
int ring[N], rcnt, ve[N], f[N], g[N], ans, pos[N];

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

bool get_ring(int x, int fa) {
    vis[x] = 1;
    for (int i = head[x]; i; i = e[i].nxt) {
        if (e[i].v == fa) continue; pre[e[i].v] = i;
        if (vis[e[i].v]) {endpos = e[i].v; return true;}
        if (get_ring(e[i].v, x)) return true; 
    }
    return false;
}

void get_ring_list() {
    int now = endpos;
    do {
        ring[++ rcnt] = now;
        ve[pre[now]] = ve[pre[now] ^ 1] = 1;
        nxt[e[pre[now]].u] = pre[now] / 2;
        now = e[pre[now]].u;
    } while (now != endpos);
    for (int i = 1; i <= rcnt; ++ i)
      ring[i + rcnt] = ring[i];
}

void dp_on_tree(int x, int fa) {
    g[x] = 0, f[x] = 0;
    for (int i = head[x]; i; i = e[i].nxt) {
        if (e[i].v == fa || ve[i] || ve[i ^ 1]) continue;
        dp_on_tree(e[i].v, x);
        f[x] = Max(f[x], f[e[i].v]);
        f[x] = Max(f[x], g[x] + g[e[i].v] + 1);
        g[x] = Max(g[x], g[e[i].v] + 1);
    }
}

int q1[N], q2[N], frt1 = 0, tal1 = -1, frt2 = 0, tal2 = -1;

int main() {
    read(n);
    for (int i = 1; i <= n; ++ i) {
        read(te[i].u), read(te[i].v);
        add_edge(te[i].u, te[i].v);
        add_edge(te[i].v, te[i].u);
    }
    get_ring(1, 0); get_ring_list(); reverse(ring + 1, ring + rcnt * 2 + 1);
    for (int i = 1; i <= rcnt; ++ i) dp_on_tree(ring[i], 0), ans = Max(ans, f[ring[i]]);
    for (int i = 1; i <= rcnt; ++ i) spc[nxt[ring[i]]] = ans;
    for (int i = 1; i <= rcnt; ++ i) {
        while (frt1 <= tal1 && g[ring[q1[tal1]]] - q1[tal1] < g[ring[i]] - i) -- tal1;
        while (frt2 <= tal2 && g[ring[q2[tal2]]] + q2[tal2] < g[ring[i]] + i) -- tal2;
        q1[++ tal1] = i, q2[++ tal2] = i;
    }
    for (int i = rcnt + 1; i <= rcnt << 1; ++ i) {
        while (frt1 <= tal1 && q1[frt1] <= i - rcnt) ++ frt1;
        while (frt2 <= tal2 && q2[frt2] <= q1[frt1]) ++ frt2;
        while (frt2 <= tal2 && g[ring[q2[tal2]]] + q2[tal2] < g[ring[i]] + i) -- tal2; q2[++ tal2] = i;
        spc[nxt[ring[i]]] = Max(spc[nxt[ring[i]]], g[ring[q1[frt1]]] - q1[frt1] + g[ring[q2[frt2]]] + q2[frt2]);
        while (frt1 <= tal1 && g[ring[q1[tal1]]] - q1[tal1] < g[ring[i]] - i) -- tal1; q1[++ tal1] = i;
    }
    for (int i = 1; i <= n; ++ i)
      if (ve[i << 1]) printf("%d\n", spc[i]);
      else printf("-1\n");
     return 0;
}

#T4 turing machine

Time Limit: 1s Memory Limit: 512MiB

#題意簡述

給定程式

read(n);read(T);
for i = 1 to n do
    read(a[i]);//a[i] must be 0 or 1
for t = 1 to T do
begin
    for i = 2 to n do
        do_swap[i]=(a[i-1]==0) and (a[i]==1)
    for i = 2 to n do
        if (do_swap[i]) then
            swap(a[i-1],a[i]);
end
for i = 1 to n do
    write(a[i],' ');

其中 \(n,T\leq10^6\),優化該程式。

#大體思路

首先理解給定的程式,是對於給定的 01 串,每次將所有前一位是 0 的 1 向前移動一位,問 \(T\) 次變化後的 01 串。

於是我們對每一個 1 進行考慮,考慮他在哪些變化中不會向前移動,顯然除了兩者開始時相鄰的情況外,在左邊的 1 不會被阻止的變化,右邊的 1 一定也不會在該次變化中被阻止,於是我們可以用佇列維護被阻止的次數。開始時所有的變化都在佇列中,顯然當兩者距離大於 \(1\) 時,由於最開始兩者同步變化,當前者停下時,兩者的距離減一次變化一定不會阻止後者;剩下的部分則是當後者停下後(變化結束後)多餘的阻擋和有效的阻擋。整體時間複雜度 \(O(n)\).

#Code

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

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

int n, m, a[N], q[N], frt, tal = -1, tot;

int main() {
    read(n), read(m);
    for (int i = m - 1; ~i; -- i) q[++ tal] = i;
    for (int i = 1, lst = 0; i <= n; ++ i) {
        int x; read(x); if (!x) continue;
        int len = i - lst - 1; ++ tot;
        q[++ tal] = -tot; tal = max(frt - 1, tal - len);
        while (frt <= tal && q[frt] + tot >= m) ++ frt;
        a[i - m + tal - frt + 1] = 1, lst = i;
    }
    for (int i = 1; i <= n; ++ i) printf("%d ", a[i]);
    return 0;
}