洛谷 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\))。这不就是求

\[\sum_{i=l}^r\sum_{k=i+1}^r f_{i,k} \]

这部分可以用二维前缀和预处理出来。

这样对于每一个询问都可以\(O(1)\)回答了。

需要注意的地方

不需要在求二维前缀和的时候判断\(i<k\)(准确说是\(i<\dots<k\Rightarrow i+1<k\)),因为不合法部分的\(f_{i,k}\)依然是\(0\).

即直接预处理

\[sum_{l,r}=\sum_{i=l}^r\sum_{k=l}^r f_{i,k} \]

对于每个询问用二维前缀和的差分

\[ans_{l,r}=sum_{r,r}-sum_{r,l-1}-sum_{l-1,r}+sum_{l-1,l-1} \]

就好了。

以及注意值域有负数,所以桶的大小要开两倍并且给所有的数都加上一个\(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;
}
posted @ 2021-03-07 16:53  Vanilla_chan  阅读(144)  评论(0)    收藏  举报