1. 程式人生 > 實用技巧 >2020牛客暑期多校訓練營(第八場)Enigmatic Partition

2020牛客暑期多校訓練營(第八場)Enigmatic Partition

2020牛客暑期多校訓練營(第八場)Enigmatic Partition

題目大意:

注意:對於每一個序列,一定要有三個不同的數,且這三個數連續,意思是一定存在 a1,a1+1,a1+2

題解:

先看上面這個圖,這個是對於長度是7,最小數為1的序列對答案的貢獻,很容易發現求貢獻的規律。

對於區間 \([10,18]\) 長度是7,最小數是1,貢獻規律就是:每隔兩個數方案數+1,到一個點之和,每隔一個數方案數-1。這個箭頭向上表示2->3 箭頭向右表示 1->2。

所以很容易推出之前的貢獻規律是對的:

  • 因為第一個向上的箭頭肯定是因為有兩個2可以向上走了,如果只有一個是肯定不行的,往上走一次就會消耗掉一個2,所以下次不能馬上向上走,而是要等一格,湊齊兩個2。
  • 到一個點之後,一定會向上走,這個是因為1被消耗的只有1個了,無法往右走了,只能往上走,往上走之後也不能往右走了,因為沒有1的生成,所以之候每一次的方案都要-1

找到這個之和,會發現這個就是一個差分陣列,然後求和。

知道這裡,其實這個題目差不多寫完了,之後的二階差分就是一種處理,也可以用線段樹代替。

接下來說說為什麼要用二階差分或者線段樹:

  • 首先明確,一定要列舉這個組成的數的長度 \(len\) 和最小的數 \(base\) ,因為這個規律就是建立在這個基礎之上的,再算算這個列舉的複雜度,列舉是 \(O(n)\) 的,最小的數看起來也是 \(O(n)\) 的,但是實際上沒有這麼大,因為每次列舉一個\(base\)
    ,是不是這個值就加了 \(len\) 這麼大,因為是三個連續的數,所以最小的數+1,那麼第二大第三大的數也要+1,所以整個 \(len\) 長的序列都+1,所以就是加了 \(len\) ,所以其實是每次跳 \(len\) 這麼長,所以複雜度就是 \(n/1+n/2+n/3+...\) 提取n,後面是一個調和級數,複雜度是 \(O(log)\) 的,所以最後複雜度就是 \(n*log\)
  • 對於每次列舉這個 \(len\)\(base\) ,只要處理是一個常數,則這個複雜度肯定是過得去的。
  • 每次列舉一個 \(len\)\(base\) ,可以直接求出這個區間的 \(l\)\(r\) 和中間點 \(x\)
    ,如果我直接構造差分陣列,那麼是不是這個區間每隔兩個數+1,\(x\) 之和每隔一個數-1,那麼這個處理的複雜度就是 \(O(r-l+1)\) 應該比 \(len\) 還大,所以這樣顯然會 \(TLE\) ,所以考慮區間更新。
  • 區間加,這個區間更新其實可以用線段樹或者差分陣列來維護,所以我們就考慮對於本來要求的這個陣列進行一個差分,這樣的話,求一次字首和就可以還原這個陣列。(這個就是二階差分,其實很好理解的,如果還是不太會可以看看程式碼,再自己理解理解)
  • 最後要明確對於不同的 \(len\)\(base\) 都可以直接加到這個差分陣列,因為對於一個數可能是由很多不同的 $len $ 或者 \(base\) 構成,這個都不影響方案數,因為方案數是直接累加的。
#include <bits/stdc++.h>
#define debug(x) cout<<"debug:"<<#x<<" = "<<x<<endl;
using namespace std;
typedef long long ll;
const int maxn = 5e5+10;
ll add[maxn],del[maxn],sum[maxn];
int main() {
    int n = 1e5;
    for (ll len = 3; len <= n; len++) {
        for (int base = 1; base * len <= n; base++) {
            int left = base * len + 3;
            add[left]++;add[left+len-2+len-3+1]--;
            del[left+len-2]++;del[left+len-2+len-3+1]--;
        }
    }
    for (int i = 3; i <= n; i++) {
        add[i] += add[i - 2];//+差分陣列
        del[i] += del[i - 1];//-差分陣列
    }
    for (int i = 3; i <= n; i++) {
        add[i] -= del[i];//差分陣列
    }
    for (int i = 3; i <= n; i++) {
        add[i] += add[i - 1];//方案數
        sum[i] = sum[i - 1] + add[i];
    }
    int T;
    scanf("%d", &T);
    for (int cas = 1; cas <= T; cas++) {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("Case #%d: %lld\n", cas, sum[r] - sum[l - 1]);
    }
}