洛谷 P6006 [USACO20JAN]Farmer John Solves 3SUM G
洛谷 P6006 [USACO20JAN]Farmer John Solves 3SUM G
Problem
什么是3-SUM?
给你一个序列\(a\),求有多少组\((i,j,k)(1\le i<j<k\le n)\)满足\(a_i+a_j+a_k=0\).
这是一个有名的算法问题,尚未发现比运行速度比平方时间明显更优的解法。
这道题给你一个长度为\(n(n\le5000)\)的序列,之后有\(q(q\le10^6)\)个询问,查询你区间\([l,r]\)的\(3-SUM\)的答案。
Solution
当你看到题目描述的时候,可能会想那位Farmer John的重大突破是啥。但是你只要知道怎么\(O(n^2)\),再想想这个\(O(n^2)\)能不能预处理,然后用更低的时间复杂度查询就好。(别以为真的有啥接近线性的算法)
首先怎么\(O(n^2)\)做呢?
枚举两个变量,对当前的第三个变量的能取的范围预先开好一个桶计数。这样我们就固定了两个变量\(i,j\),去桶里面找\(-a_i-a_j\)的数量即可。
如果你是枚举\(i,j\),对k的取值范围\([j+1,n]\)开桶的话,当然也可以但是这个k的限制并不显然(或者是也有向下做的方法只是我没有想到吧)
如果我们枚举\(i,k\),这样\(k\)的取值范围就是\([i+1,k-1]\)。记\(c_x\)表示在当前区间内,等于\(x\)的\(a_k\)的数量;设\(f_{i,k}\)表示选定左端点为\(i\),右端点为\(k\)时的3-SUM方案数。
for(int i=1;i<=n;i++)
{
c[a[i+1]+M]++;
for(int k=i+2;k<=n;k++)
{
if(a[i]+a[k]<=M&&a[i]+a[k]>=-M) f[i][k]+=c[M-a[i]-a[k]];
c[a[k]+M]++;
}
c[a[i+1]+M]--;
for(int k=i+2;k<=n;k++)
{
c[a[k]+M]--;//clear
}
}
代码中的c是桶,M可以先不管(见下文)
对于一个询问\((l,r)\),这里的\(i,k\)当然可以选\([l,r]\)中的任意值(只要满足\(i<k\))。这不就是求
这部分可以用二维前缀和预处理出来。
这样对于每一个询问都可以\(O(1)\)回答了。
需要注意的地方
不需要在求二维前缀和的时候判断\(i<k\)(准确说是\(i<\dots<k\Rightarrow i+1<k\)),因为不合法部分的\(f_{i,k}\)依然是\(0\).
即直接预处理
对于每个询问用二维前缀和的差分
就好了。
以及注意值域有负数,所以桶的大小要开两倍并且给所有的数都加上一个\(10^6\)。
Code
/**************************************************************
* Problem: 6006
* Author: Vanilla_chan
* Date: 20210307
**************************************************************/
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<limits.h>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#ifdef TH
#define debug printf("Now is %d\n",__LINE__);
#else
#define debug
#endif
#ifdef ONLINE_JUDGE
char buf[1<<23],* p1=buf,* p2=buf,obuf[1<<23],* O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
using namespace std;
template<class T>inline void read(T& x)
{
char ch=getchar();
int fu;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)) { x=x*10+ch-'0';ch=getchar(); }
x*=fu;
}
inline int read()
{
int x=0,fu=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)) { x=x*10+ch-'0';ch=getchar(); }
return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do { G[++g]=x%10;x/=10; } while(x);
for(int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
#define N 6010
int n,q;
int a[N];
LL f[N][N];
int c[2000010];
#define M 1000000
int main()
{
n=read();
q=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++)
{
c[a[i+1]+M]++;
for(int k=i+2;k<=n;k++)
{
if(a[i]+a[k]<=M&&a[i]+a[k]>=-M) f[i][k]+=c[M-a[i]-a[k]];
c[a[k]+M]++;
}
c[a[i+1]+M]--;
for(int k=i+2;k<=n;k++)
{
c[a[k]+M]--;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
}
}
int x,y;
while(q--)
{
x=read();
y=read();
if(x>y) swap(x,y);
write(f[y][y]-f[x-1][y]-f[y][x-1]+f[x-1][x-1]);
}
return 0;
}

浙公网安备 33010602011771号