2024暑假集訓測試1

卡布叻_周深發表於2024-07-09

前言

  • 比賽連結

image

排名歷程:\(3→5→3\),因為 \(T1\)special judge 是後來加上的,導致部分人掛了分,賽後安排了重測,就變成了 \(rank5\),賽後發現 \(T1\) 資料過水,重新更新了資料,卡掉了很多人的假做法,又成了 \(rank3\)

T1

已知合法的分組有 \(\begin{cases} 0~0~0\\ 1~1~1\\ 2~2~2\\ 0~1~2\\ \end{cases}\)

  • 唐氏做法:

    統計 \(cnt_0,cnt_1,cnt_2\) ,找規律發現除了其中兩個 \(\bmod 3=2\) ,剩下一個 \(\bmod 3<2\) 時,將 \(<2\) 的一組中拆出來兩個補給其餘兩個形成 \(0~1~2\) ,這樣 \(ans-1+2\),更優。

    其餘情況均為將其各自分組,最後餘下的形成 \(0~1~2\) 即可。

    由此 \(if~else\) 大模擬即可。

  • 正解:

    考慮列舉 \(0~1~2\) 的組數即可。

T2

  • 部分分 \(30pts\)\(n\le 1e7\) 的情況,暴力即可。

T3

  • 部分分 \(30pts\):爆搜即可。

  • 部分分 \(40pts\):再處理一下 \(k=1\) 的情況即可。

  • 正解 \(100pts\)

    • 結論:

      忽略字典序,將所有 & 放在前面,所有 | 放在後面一定是最優情況。

      • 證明:

        \(\begin{cases} x|y\ge \max(x,y)\\ x\&y\le \min(x,y)\\ x\&y\le x|y\\ \end{cases}\)

        因此有 \(x\&y|z\ge \max(x\&y,z)\ge \min(x|y,z)\ge x|y\&z\)

        解釋一下,若 \(x\&y\ge z\)\(\because x|y\ge x\&y\)\(∴ \max(x\&y,z)=x\&y\ge z=\min(x|y,z)\)

        反之若 \(x\&y<z\)\(\because x|y\ge x\&y\)\(∴ \min(x|y,z)\ge z=\max(x\&y,z)\)

        由此繼續推廣,得上述結論,證畢。

    因此根據上述結論我們可以先求出最大答案,接下來處理字典序的問題。

    從前往後推,若該位填 |,後面的依然按照結論進行,若答案不變,則本位可以填 |,否則填 &

    由此 \(O(n^2)\) 演算法已經可以得到 \(60pts\) 了。

    考慮 \(a_i<2^{60}\) ,可以按位計算,當計算到第 \(i\) 位時,對於區間 \([l,r]\),若填 &,只有 \(a_l\sim a_r\) 本位均為 \(1\) 時答案本位才能為 \(1\),否則為 \(0\);若填 |,只要 \(a_l\sim a_r\) 中有一個本位為 \(1\) 答案本位就可以為 \(1\),否則為 \(0\)

    由此演算法最佳化到 \(O(n\log(n))\),可以透過此題。

    點選檢視程式碼
    #include<bits/stdc++.h>
    #define int long long
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=2e5+10,M=61;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
    void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
    int n,k,a[N],maxx,m,ans,now,b[M],sum[N][M];
    int check(int l,int now,int k)
    {
        if(l<=n-k)
        {
            for(int i=0;i<=m;i++) b[i]=sum[n-k][i]-sum[l-1][i];
            for(int i=0;i<=m;i++) 
                if(b[i]!=n-k-l+1&&(now>>i)&1)
                    now^=(1ll<<i); 
        }
        if(n-k+1<=n)
        {
            for(int i=0;i<=m;i++) b[i]=sum[n][i]-sum[n-k][i];
            for(int i=0;i<=m;i++)
                if(b[i]!=0) 
                    now|=(1ll<<i);
        }
        return now;
    }
    signed main()
    {
        read(n),read(k);
        for(int i=1;i<=n;i++) read(a[i]),maxx=max(maxx,a[i]);
        m=log2(maxx);
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=m;j++) sum[i][j]=sum[i-1][j];
            for(int j=0;j<=m;j++) if((a[i]>>j)&1) sum[i][j]++;
        }
        ans=check(2,a[1],k);
        write(ans),puts("");
        now=a[1];
        for(int i=2;i<=n;i++)
            if(k>=1&&check(i+1,now|a[i],k-1)==ans)
                putchar('|'),now|=a[i],k--;
            else 
                putchar('&'),now&=a[i];
    }
    

T4

  • 賽時部分分都沒打出來,直接看正解吧。

  • 正解:

    預處理一個布林陣列 \(ok_{l,r}\),表示區間 \([l,r]\) 能否全部刪掉。

    區間 \(DP\) 處理,有:

    \[ok_{l,r}|=(ok_{l,k-1}\&ok_{k+1,r})\&(\gcd(a_{l-1},a_k)|\gcd(a_k,a_{r+1})),k∈[l,r] \]

    初始值: \(\begin{cases} ok_{l,r}=1&l>r\\ ok_{i,i}=1&\gcd(a_{i-1},a_i)>1 || \gcd(a_i,a_{i+1})>1\\ \end{cases}\)

    此時需要假設 \(a_{l-1},a_{r+1}\) 沒有被刪掉,貌似假了,但是看到下面的 \(DP\) 式子就知道其是正確的了。

    \(f_i\) 表示若 \(a_i\) 不刪,前 \(i\) 個元素中最多能刪多少個,有:

    \[f_i=\max_{j=0}^{i-1}\{f_j+ok_{j+1,i-1}\times (i-j-1)\} \]

    答案為 \(f_{n+1}\)

    預處理 \(\gcd\),複雜度為 \(O(n^3)\)

    點選檢視程式碼
    #include<bits/stdc++.h>
    #define int long long
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=510;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
    void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
    int n,a[N],gcd[N][N],f[N];
    bool ok[N][N];
    int Gcd(int a,int b) {return b==0?a:Gcd(b,a%b);}
    signed main()
    {
        read(n);
        for(int i=1;i<=n;i++) read(a[i]);
        for(int i=1;i<=n;i++)
            for(int j=i;j<=n;j++)
                gcd[j][i]=gcd[i][j]=Gcd(a[i],a[j]);
        for(int i=1;i<=n;i++)
            for(int j=1;j<i;j++)
                ok[i][j]=1;
        for(int i=1;i<=n;i++)
            if(gcd[i][i+1]>1||gcd[i-1][i]>1) ok[i][i]=1;
        for(int i=1;i<=n;i++)
            for(int l=1;l+i-1<=n;l++)
            {
                int r=l+i-1;
                for(int k=l;k<=r;k++)
                {
                    ok[l][r]|=(ok[l][k-1]&ok[k+1][r])&(gcd[k][l-1]>1|gcd[k][r+1]>1);
                    if(ok[l][r]==1) break;
                }
            }
        for(int i=1;i<=n+1;i++)
            for(int j=0;j<=i-1;j++)
                    f[i]=max(f[i],f[j]+(i-j-1)*ok[j+1][i-1]);
        write(f[n+1]);
    }
    

T5