AtCoder Beginner Contest 370 補題記錄

klr_i發表於2024-09-10

A - Raise Both Hands

題意:

給出Snuke舉的左右手情況,如果只舉左手,輸出Yes,如果只舉右手,輸出No,否則輸出Invalid

思路:

  • 舉左手:(l == 1 && r == 0)
  • 舉右手:(l == 1 && r == 0)
  • 其他情況都是Invalid
void solve()
{
    int l = read(), r = read();
    if(l == 1 && r == 0){
        cout<<"Yes"<<endl;
    }else if(l == 0 && r == 1){
        cout<<"No"<<endl;
    }else { 
        cout<<"Invalid"<<endl;
    }
}

B - Binary Alchemy

題意:

\(N\) 種編號為 \(1, 2, \ldots, N\) 的元素。

元素之間可以相互組合。當元素 \(i\)\(j\) 組合在一起時,如果 \(i \geq j\) 變為元素 \(A_{i, j}\) ,如果 \(i < j\) 變為元素 \(A_{j, i}\)

從元素 \(1\) 開始,依次與元素 \(1, 2, \ldots, N\) 結合。求最後得到的元素。

思路:

根據題意,模擬查表,逐一合成即可

void solve()
{
    int n = read();
    int a[N][N];
    for(int i=1;i<=n;i++) for(int j=1;j<=i;j++) a[i][j] = read();

    int x = 1;
    for(int t=1;t<=n;t++){
        int i = max(x,t) , j = min(x,t);
        x = a[i][j];
    }

    cout<<x<<endl;
}

C - Word Ladder

題意:

給定兩個長度相等的字串 \(S\)\(T\),
用最小的操作次數,使得 \(S = T\) ,並且字串 \(X\) 的字典序最小。
操作為,選擇 \(S_i = c\) ,並且將修改後的 \(S\) 放入 \(X\) 的末尾。

思路:

  • 顯然S和T有多少個不同的字元就操作多少次就是次數最少
  • 考慮使X的字典序最小,在S中修改字元,越靠前並且越小的字元應該越靠後修改
  • 故兩次遍歷S,先從前往後遍歷,若\(S_i < T_i\)則操作一次
  • 再從後往前遍歷,將剩下不一樣的字元修改掉
void solve()
{
    string s1 = sread(), s2 = sread();
    int n = s1.length();

    vector<string> ans;
    for(int i=0;i<n;i++){
        if(s1[i] > s2[i]){
            s1[i] = s2[i];
            ans.push_back(s1);
        }
    }

    for(int i=n-1;i>=0;i--){
        if(s1[i]!=s2[i]){
            s1[i] = s2[i];
            ans.push_back(s1);
        }
    }
    
    cout<<ans.size()<<endl;
    for(auto it:ans) cout<<it<<endl;
}

D - Cross Explosion

題意:

\(H \times W\) 的二維網格,初始所有格子都有牆
\(Q\) 次操作,每次操作都在\((x , y)\)處放一炸彈

  • \((x , y)\)沒有牆,則炸燬牆
  • \((x , y)\)有牆,則分別炸燬上下左右的最近的第一面牆

思路:

對於每一個座標\((x , y)\),可以分成行和列來考慮

  • 在第x行中,可以用二分查詢找到第一個座標大於等於y的牆
  • 在第y列中,可以用二分查詢找到第一個座標大於等於x的牆
    因此,我們需要一個 既能二分查詢,也能插入,刪除的容器
    顯然只有 STL::set 能夠滿足
    將行和列分別建立set陣列,每次操作在行和列都執行即可
void solve()
{
    string s1 = sread(), s2 = sread();
    int n = s1.length();

    vector<string> ans;
    for(int i=0;i<n;i++){
        if(s1[i] > s2[i]){
            s1[i] = s2[i];
            ans.push_back(s1);
        }
    }

    for(int i=n-1;i>=0;i--){
        if(s1[i]!=s2[i]){
            s1[i] = s2[i];
            ans.push_back(s1);
        }
    }
    
    cout<<ans.size()<<endl;
    for(auto it:ans) cout<<it<<endl;
}

E - Avoid K Partition

題意:

給定一個陣列 \(A\),劃分成若干個子區間,使得沒有子區間的和為 \(K\)
求劃分方案數

思路:

涉及到子區間的和,顯然要用到字首和來最佳化,預處理一個字首和陣列 pre[N]

  • 線性DP: (會TLE)
    \(f_i\) 表示當前列舉到的連續子序列以 \(i\) 結尾的不同方案數
    • \(i\) 從1到n遍歷
    • \(j\) 從0到i-1,pre[i] - pre[j] 即為最後一個子序列的和,若該值不等於K,則將該劃分方案加入到 f[i]中
void solve()
{
    int n = read(), k = read();
    const int mod = 998244353;

    vector<int> v(1);
    for(int i=1; i<=n; i++) v.push_back(read());

    vector<int> f(n+1),pre(n+1);

    for(int i=1;i<=n;i++){
        pre[i] = v[i] + pre[i-1];
    }

    f[0] = 1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<i;j++){
            if(pre[j] != pre[i] - k){
                f[i] = (f[i] + f[j])%mod;
            }
        }
    }

    cout<<f[n]<<endl;
}
  • 考慮最佳化,pre[j] 只要不等於 pre[i] - k 即可,因此不需要遍歷每個子區間,只需要在所有方案的和中減去子區間和是k的方案即可
    • 用map來儲存不同子區間和的方案的個數,即pre[i]的方案個數(方案個數就是符合條件的f[i]的值)
    • 用sum來記錄所有子序列的劃分方案的和
void solve()
{
    int n = read(), k = read();
    vector<int> v(1);
    for(int i=0;i<n;i++) v.push_back(read());

    const int mod = 998244353;
    vector<int> pre(n+1),f(n+1);
    unordered_map<int,int> cnt;
    for(int i=1;i<=n;i++) pre[i] = pre[i-1] + v[i];

    f[0] = 1;
    cnt[0] = 1;
    int sum = 1;
    for(int i=1;i<=n;i++){
        f[i] = sum - cnt[pre[i] - k];
        f[i]%=mod, f[i]+=mod, f[i]%=mod;
        sum = (sum+f[i])%mod;
        cnt[pre[i]] += f[i];
    }

    cout<<f[n]<<endl;
}
  • 注意到:若沒有和為 \(K\) 的條件,長度為n的陣列劃分方案的個數為2的n次方

相關文章