【題解】洛谷P6006 [USACO20JAN]Farmer John Solves 3SUM G

題意

傳送門

題解

發現n5000n \leq 5000,那麼我們自然想到O(n2)O(n^2)預處理之後O(1)O(1)回答詢問。

先考慮一個更簡單的問題,如果f[i][j]f[i][j]表示在區間[l,r][l,r]中,滿足k(l,r)a[k]+a[l]+a[r]=0k \in (l,r),a[k]+a[l]+a[r]=0kk的數量,那麼我們是可以枚舉左右端點,用一個桶做到O(n2)O(n^2)預處理ff的。

那麼ff與最後的答案是什麼關係呢?最後要求的是:在一段區間內,左右端點不強制選的方案數。這隱隱約約的有點像是ff數組的一個前綴和。

我們可以考慮先求出s[l][r]s[l][r]表示左端點在[1,l][1,l]內,右端點在[1,r][1,r]內的總方案數。這就真的是ff的二位前綴和了。

可以這麼理解,把f[l][r]f[l][r]對應到平面上的一個點,那麼s[l][r]s[l][r]就是從(1,1)(1,1)(l,r)(l,r)的這個矩形中所有點的和。這樣我們也就可以在O(n2)O(n^2)的時間內求出ss

同樣的,最後的答案實際上是區間左右端點都在[l,r][l,r]內總答案。對應到平面上也就是左上角爲(l,l)(l,l),右下角爲(r,r)(r,r)的矩形中所有點的和。這樣就可以通過ssO(1)O(1)求答案了。

代碼

注意開桶的時候需要平移值域

#include <bits/stdc++.h>
#define ll long long
#define MAX 5005
#define K 1000000
using namespace std;

int n, Q;
int a[MAX], cnt[2000005];
ll s[MAX][MAX];

int main()
{
    cin >> n >> Q;
    for(int i = 1; i <= n; ++i){
        scanf("%d", &a[i]), a[i] += K;
    }
    for(int i = 1; i <= n; ++i){
        for(int j = i+1; j <= n; ++j){
            if(j > i+1){
                if(a[i]+a[j] <= K*3 && a[i]+a[j] >= K) s[i][j] = cnt[K*3-a[i]-a[j]];
            }
            cnt[a[j]]++;
        }
        for(int j = i+1; j <= n; ++j){
            cnt[a[j]]--;
        }
    }
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            s[i][j] += s[i-1][j]+s[i][j-1]-s[i-1][j-1];
        }
    }
    int l, r;
    while(Q--){
        scanf("%d%d", &l, &r);
        printf("%lld\n", s[r][r]-s[l-1][r]-s[r][l-1]+s[l-1][l-1]);
    }

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章