AtCoder Beginner Contest 369 補題記錄

klr_i發表於2024-09-01

A - 369

題意:

給定A和B,求有多少個x可以和A,B構成等差數列

思路:

分三種情況討論

  1. A == B
    則x不得不與A和B想等
  2. x位於A和B中間
    只有B - A 為偶數才有這種情況存在
  3. x位於A和B兩邊
    可以在左邊也可以在右邊,只要A!=B這種情況總會存在
void solve()
{
    int a = read(), b = read();

    if(a == b){
        cout<<1<<endl;
    }else {
        if((b-a)&1){
            cout<<2<<endl;
        }else {
            cout<<3<<endl;
        }
    }
}

B - Piano 3

題意:

高橋有一架鋼琴,上面有 \(100\) 個排成一行的琴鍵。從左邊開始的 \(i\) 個鍵叫做 \(i\) 個鍵。
他會逐個按下 \(N\) 個鍵來彈奏音樂。在按下 \(i\) /th鍵時,如果 \(S_i=\) L,他會用左手按下 \(A_i\) 鍵,如果 \(S_i=\) R,則用右手按下 \(A_i\) 鍵。
開始演奏前,他可以將雙手放在任何他喜歡的鍵上,此時他的疲勞度為 0。在演奏過程中,如果他將一隻手從鍵 \(x\) 移到鍵 \(y\) 上,疲勞度會增加 \(|y-x|\) (反之,除了移動手以外,疲勞度不會增加)。用手按下某個鍵時,該手必須放在該鍵上。
找出表演結束時可能的最低疲勞度。

思路:

考慮到

  • \(1 \leq N \leq 100\)
  • \(1 \leq A_i \leq 100\)

直接暴力列舉每個按鍵為初始按鍵,左右手各列舉100個鍵,再遍歷n遍,最高1e6

程式碼:

void solve()
{
    int n = read();
    vector<pair<int,char> > v;
    for(int i=0;i<n;i++){
        int a = read();
        char c; cin>>c;
        v.push_back({a,c});
    }

    int ans = INF;
    for(int i=1;i<=100;i++) for(int j=1;j<=100;j++){
        int ret = 0, l = i, r = j;
        for(auto it:v){
            if(it.se == 'L'){
                ret += abs(l - it.fi);
                l = it.fi;
            }else {
                ret += abs(r - it.fi);
                r = it.fi;
            }
        }
        ans = min(ret,ans);
    }

    cout<<ans<<endl;

}

C - Count Arithmetic Subarrays

題意:

給一個長度為n的序列,求能構成等差數列的子序列的個數

思路:

  • 顯然每個長度為1和長度為2的子序列都能構成等差數列,所以答案至少是 \(n+(n-1)\)
  • 再去找每個長度大於等於3並且能構成等差數列的子序列,長度為n的等差數列子序列能拆分成 \(1+2+3+...+n\) 個等差子序列,去除已經計算的長度為1和2的子序列,能拆分成 \(1+2+3+...+(n-2)\)
  • 找等差子序列的過程類似於滑動視窗,維護 \(l\)\(r\) 兩個指標即可

程式碼:

void solve()
{
    int n = read();
    vector<int> v;
    for(int i=0;i<n;++i) v.emplace_back(read());
    
    int ans = n + n - 1;
    if(n <= 2){
        cout<<ans<<endl;
        return;
    }
    int l = 0 , r = 1 , d = v[r] - v[l];
    bool flag = 0;
    while(r < n && l < n){
        int dt = v[r] - v[r-1];
        if(dt == d){
            flag = 1;
        }else {
            flag = 0;
            if(r - l > 2){
                int t = r - l - 2;
                ans += (t+1)*t/2;
            }
            l = r - 1;
            d = v[r] - v[l];
        }
        r++;
    }
    if(r - l > 2 && flag){
        int t = r - l - 2;
        ans += (t+1)*t/2;
    }
    cout<<ans<<endl;
}

D - Bonus EXP

題意:

給一個長度為n的序列,在序列中從前往後取數。對於序列中的每個數,可以取也可以不取。對於所有取了的數,將所有第偶數次取的數乘2,再將所有取了的數求和,求該如何取數使這個和最大

思路:

線性dp

  • 對於序列中每個數和上一個數之間的關係,只有取的次序是奇數還是偶數對該數有影響
  • 所以只需要維護取到每個數的兩種狀態值,即到奇數次還是偶數次
    • 奇數次:
      1. 上一個數是偶數次,本次取這個數
      2. 上一個數是奇數次,本次不取
    • 偶數次:
      1. 上一個數是奇數次,本次取這個數
      2. 上一個數是偶數次,本次不取
  • 狀態轉移方程:
    dp[i][0] = max(dp[i-1][1] + 2*v[i], dp[i-1][0]); //偶數次
    dp[i][1] = max(dp[i-1][0] + v[i], dp[i-1][1]); //奇數次

程式碼:

void solve()
{
    int n = read();
    vector<int> v;
    for(int i=0;i<n;++i) v.emplace_back(read());
    
    vector<vector<int> > dp(n,vector<int>(2));
    dp[0][1] = v[0];
    dp[0][0] = 0;
    for(int i=1;i<n;i++){
        dp[i][0] = max(dp[i-1][1] + 2*v[i], dp[i-1][0]);
        dp[i][1] = max(dp[i-1][0] + v[i], dp[i-1][1]);
    }

    int ans = max(dp[n-1][1],dp[n-1][0]);

    cout<<ans<<endl;
}

E - Sightseeing Tour

思路:

全排列+floyd

F - Gather Coins

思路:

  • 第一維排序,第二維度LIS
  • 用二分或樹狀陣列最佳化LIS,達到 \(O(nlogn)\) 複雜度

G - As far as possible

思路:

  • 行走的長度 為 \(根到頂點的每個路徑的集合的總權值*2\)
  • 樹剖-長鏈剖分,每個頂點指向最深的葉子頂點

相關文章