P11234 [CSP-S 2024] 擂臺遊戲 題解

max0810發表於2024-10-27

P11234 [CSP-S 2024] 擂臺遊戲 題解

前言

作者在考場上用了約 1h 把前三道題做完了,然後用了約半小時想了帶 \(\log\) 的做法,但是我決定放手一搏去想線性的做法,於是又想了有 1h 之後覺得想到了正解,然後我就一直寫到了考試結束,但是最終沒有調出來遺憾離場,因此寫個題解來紀念一下。

題解

首先因為 \(n,m\) 是同階的,所以肯定是考慮把所有位置的答案全部算出來。首先,設 \(N\) 為第一個不小於 \(n\)\(2\) 的整數次冪,我們用 \(N-n\)\(0\) 去補齊剩下的位置,第一步肯定是把所有輪獲勝的人用一棵線段樹建出來,這一步是好做的。

現在考慮依次求出每個位置的答案 \(ans_i\),我第一想法是正著去掃,但是感覺不太好做,所以決定倒著去掃。有一個顯然的結論就是,如果一個位置從一個確定的數變成了任意值,那麼原來在答案裡的人現在依然在答案裡,也就是說,對於二進位制位數相同的一段,\(ans_i\) 是不增的,於是現在就考慮計算如果一個位置由確定值變成了任意值,有哪些新的可能的答案。當然,對於二進位制位數不同的段,每次都要清空並重新計算,以下僅考慮對於同一個段的計算。

首先,我們線段樹先預處理出了每個結點是哪一個人會獲勝,對於當前的段,答案裡肯定有本來就會獲勝的那一個人,所以先講其計入答案(注意,以下說的計入答案都指的是先判斷這個人是否已被計入答案再加,因為可能有重複)。

接著,如果對於一個線段樹的結點,滿足以下條件,我們就將其稱為壞點:這個區間的 \(d=0\),且編號小的人會獲勝。如果一個結點是壞點,那麼對於這個結點右兒子中包含的所有的人,他們由確定值變為任意值對答案都是沒有貢獻的,因為到這個結點的時候他們一定都會被打敗(所有人是從後往前一個一個變為不確定的,所以一個人變之後前面的所有人的值依然是確定的)。那麼我們可以給整個區間打上標記,表示這個區間都沒有貢獻。如果掃到了一個被打標記的人,就直接跳過。

否則的話,這個人一定是有貢獻的,因為只要將他的初始值設為 \(\infty\),他就一定會贏,將他計入答案。接著考慮哪些人也會跟著計入答案,我們考慮從當前這個人代表的葉子結點不斷往上跳。設當前點為 \(x\),父親為 \(fa_x\),我們分 \(x\)\(fa_x\) 的左兒子還是右兒子兩種情況來考慮:

  • \(x\) 是右兒子,那麼 \(fa_x\) 的左兒子結點的勝者 \(y\) 就有可能會被記入答案,具體的,我們對於每一個結點都再記錄一個值 \(mx\),表示根到這個結點的所有對局中,\(d\) 為選到它的所有對局中輪數最大的是多少,這個也可以在預處理中簡單計算。那麼如果 \(a_y\ge mx\)\(y\) 就能被計入答案,因為我們可以將所有的任意值設為剛好在與 \(y\) 對局時輸掉,這樣就能獲勝。
  • \(x\) 是左兒子且 \(fa_x\) 是一個壞點,那麼此時 \(fa_x\) 右兒子中的所有人都已經變成了任意值,他們都應該被計入答案。可以理解為這些人原來有個限制使得他們不能獲勝,現在這個限制沒了,所以這些人都可以獲勝了。
  • \(x\) 是左兒子且 \(fa_x\) 不是一個壞點,那麼在繼續往上跳的貢獻都已經被 \(fa_x\) 的右兒子計算過了,直接 break。

於是,我們就計算完了所有的貢獻,每次在一個人變之前,讓 \(ans_i\) 賦為當前的答案即可。

考慮時間複雜度,首先建樹什麼的都是線性的,然後對於計算答案的部分,我們考慮對於每一個線段樹的結點,它不可能同時被左兒子和右兒子訪問(因為被左兒子訪問要求這是個壞點,但是如果是壞點的話右兒子直接被打上標記跳過了),所以每個結點都只會被訪問一次,所以複雜度是 \(\mathcal{O}(Tn)\) 的。

另外,還有一個需要注意的問題:如果每一個二進位制位數不同的段都預處理 \(mx\) 的話常數太大了,我們發現每個結點的 \(d\) 是一開始給定的,所以這個只需要在 \(T\) 組資料前預處理即可。其他細節,比如讀入之類的具體可以看程式碼。

程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ls x<<1
#define rs x<<1|1
#define lson x<<1,l,mid
#define rson x<<1|1,mid+1,r
#define ll long long
using namespace std;
const int N = (1<<17)+5;
int mx[20][N<<2],lg[N],aa[N],ok[N],a[N],c[N],n,m,nn = 1;
bool vis[N];ll ans[N],sum;
struct node{ll sum;int x;bool tp,d;}t[N << 2];
inline void build(int x,int l,int r)
{
    if(l == r){t[x].x = l;return ;}
    int mid = l+r>>1,k = lg[r-l+1],d = t[x].d;
    build(lson);build(rson);
    int win = a[t[x<<1|d].x] >= k;
    t[x].x = t[x<<1|(d^!win)].x;
    if(!d&&win)t[x].tp = 1,ok[r]++,ok[mid]--;
    else t[x].tp = 0;
}
inline void build2(int x,int l,int r,int *mx)
{
    t[x].sum = (l+r)*(r-l+1ll)/2;
    if(l == r)return ;
    int mid = l+r>>1,k = lg[r-l+1],d = t[x].d;
    if(~mx[x])mx[ls] = mx[rs] = mx[x];
    else mx[x<<1|d] = k;
    build2(lson,mx);build2(rson,mx);
}
inline void up(int x){sum += !vis[x]*x;vis[x] = 1;}
inline int rd()
{
    char c;int f = 1;
    while(!isdigit(c = getchar()))if(c=='-')f = -1;
    int x = c-'0';
    while(isdigit(c = getchar()))x = x*10+(c^48);
    return x*f;
}
inline char gc()
{char c;while((c = getchar()) <= ' ');return c;}
int main()
{
    // freopen("arena.in","r",stdin);
    // freopen("arena.out","w",stdout);
    n = rd();m = rd();
    for(int i = 1;i <= n;i++)aa[i] = rd();
    for(int i = 1;i <= m;i++)c[i] = rd();
    while(nn < n)nn <<= 1;
    for(int i = 2;i <= nn;i++)lg[i] = lg[i>>1]+1;
    for(int i = nn/2;i;i >>= 1)
        for(int j = i;j < i*2;j++)
            t[j].d = gc()-'0';
    memset(mx,-1,sizeof mx);
    for(int s = 0;(1<<s) <= nn;s++)
        build2(1<<s,1,nn>>s,mx[s]);
    for(int T = rd();T--;)
    {
        int yh[4] = {rd(),rd(),rd(),rd()};
        for(int i = 1;i <= n;i++)a[i] = aa[i]^yh[i&3];
        memset(ok,0,sizeof ok);build(1,1,nn);
        for(int i = nn,rt = 1,s = 0;i;i--)
        {
            if((1<<lg[i]) == i)
            {
                if(i != nn)rt <<= 1,s++;
                for(int j = 1;j <= i;j++)vis[j] = 0;
                sum = 0;up(t[rt].x);
            }
            ans[i] = sum;
            if(ok[i] += ok[i+1])continue;
            up(i);int x = i+nn-1;
            while(x != rt)
            {
                int d = x&1;x >>= 1;
                if(!d)
                    if(t[x].tp)sum += t[rs].sum;
                    else break;
                else if(a[t[ls].x] >= mx[s][ls])up(t[ls].x);
            }
        }
        ll num = 0;
        for(int i = 1;i <= m;i++)num ^= i*ans[c[i]];
        printf("%lld\n",num);
    }
    return 0;
}

總結

這是一道細節很多的好題,我覺得考場上至少再給我個 1h 才能寫出來,但是我覺得能想出來已經很不錯了,NOIP 再戰吧!

關於程式碼或做法有任何問題的歡迎私信我!