首页 > 其他分享 >[USACO23FEB] Equal Sum Subarrays G 题解

[USACO23FEB] Equal Sum Subarrays G 题解

时间:2023-11-14 14:24:22浏览次数:38  
标签:p1 s2 Sum top2 Subarrays MAXN 题解 区间 s1

[USACO23FEB] Equal Sum Subarrays G 题解

题目链接

\(O(n^5)\) 暴力

显然,如果修改 \(a_i\) 的值,只会影响包含 \(a_i\) 的区间的区间和。于是对于每个 \(a_i\),可以将所有区间分成两类,即包含 \(a_i\) 的区间和不包含 \(a_i\) 的区间。这两种区间的区间和中最小的差值即为答案。

对于每个 \(a_i\),暴力枚举所有的区间和是 \(O(n^2)\) 的(这里可以用前缀和快速求出区间和);枚举两类区间的差值是 \(O(n^4)\) 的——注意区间的个数是 \(O(n^2)\) 量级的,双层的遍历就是 \(O(n^4)\)。乘上外层枚举 \(a_i\) 的循环,总的时间复杂度为 \(O(n^5)\)。

部分代码如下:

// s1[]:包含 a[i] 的区间和,s2[]:不包含 a[i] 的区间和
// top1 和 top2 分别表示 s1[] 和 s2[] 当前的元素个数
for(int i = 1; i <= n; i++)
{
    top1 = top2 = 0, ans = INF;
    for(int j = 1; j <= n; j++)
    {
        for(int k = j; k <= n; k++) // 枚举 [j, k] 区间
        {
            ll s = sum[k] - sum[j-1];
            if(j <= i && k >= i) s1[++top1] = s; // 判断该区间是否包含 a[i]
            else s2[++top2] = s;
        }
    }
    for(int i = 1; i <= top1; i++) // O(n^4) 暴力枚举差值
        for(int j = 1; j <= top2; j++) ans = min(ans, abs(s1[i] - s2[j]));
    printf("%lld\n", ans);
}

\(O(n^3 \log (n^2))\)

上面这种做法中用 \(O(n^4)\) 暴力枚举差值实在太过愚蠢了。考虑优化这一步。

先将 s1[],s2[] 排序,然后使用双指针法,这样只要遍历一遍,时间复杂度降低到 \(O(n^2)\)。

这部分代码如下:

sort(s1 + 1, s1 + top1 + 1), sort(s2 + 1, s2 + top2 + 1);
int p1 = 1, p2 = 1; // p1 和 p2 分别是 s1[] 和 s2[] 的指针
s2[top2+1] = INF; // 由于双指针法可能取到 s2[top2+1],需要先将其设为无穷大
for(p1; p1 <= top1; p1++)
{
    while(s2[p2+1] <= s1[p1] && p2 < top2) p2++;
    ans = min({ans, abs(s2[p2] - s1[p1]), abs(s2[p2+1] - s1[p1])});
    // s2[p2] 是 s2[] 中小于等于 s1[p1] 的最大的数,s2[p2+1] 是 s2[] 中大于 s1[p1] 的最小的数
    // 由于 s1[] 和 s2[] 都是升序的,所以与 s1[p1] 差值最小的数一定在这两者之中 
}

给 \(n^2\) 个数排序是 \(O(n^2 \log (n^2))\) 的,双指针法是 \(O(n^2)\),总的时间复杂度为 \(O(n^3 \log (n^2))\)。

\(O(n^3)\)

上面的做法的时间瓶颈来自给 \(n^2\) 个数排序。如何优化呢?

我们发现 \(n\) 的范围很小,这启示我们可以把 \(O(n^2)\) 个区间和预处理,将其离散化和排序,之后每次排序 s1[],s2[] 时就可以用桶排,时间复杂度降到 \(O(n^2)\)。

具体做法详见代码:

// P9127 [USACO23FEB] Equal Sum Subarrays G
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int MAXN = 510;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, top0, top1, top2;
bool vis1[MAXN * MAXN], vis2[MAXN * MAXN];
ll a[MAXN], sum[MAXN], s1[MAXN * MAXN], s2[MAXN * MAXN];
ll q[MAXN * MAXN], tot, rnk[MAXN][MAXN];

void pre()
{
	// 预处理,离散化,排序 
	for(int i = 1; i <= n; i++)
	{
		for(int j = i; j <= n; j++) q[++tot] = sum[j] - sum[i-1];
	}
	sort(q + 1, q + 1 + tot);
	top0 = unique(q + 1, q + 1 + tot) - q - 1;
	// q 是离散化后的数组 
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			ll s = sum[j] - sum[i-1];
			rnk[i][j] = (lower_bound(q + 1, q + 1 + top0, s) - q);
			// rnk[i][j] 表示 [i,j] 的区间和的排名,即新的下标
			// q[rnk[i][j]] 即为 [i,j] 的区间和 
		}
	}	
}

ll solve(int pos)
{
	ll ans = INF;
	memset(vis1, 0, sizeof(vis1)), memset(vis2, 0, sizeof(vis2));
	top1 = top2 = 0, ans = INF;
	for(int i = 1; i <= n; i++)
	{
		for(int j = i; j <= n; j++) // [i, j]
		{
			if(i <= pos && j >= pos) vis1[rnk[i][j]] = true;
			else vis2[rnk[i][j]] = true;
		}
	}
	for(int i = 1; i < MAXN * MAXN; i++) // 桶排序
	{
		if(vis1[i]) s1[++top1] = q[i];
		else if(vis2[i]) s2[++top2] = q[i];
	}
	int p1 = 1, p2 = 1; // p1 和 p2 分别是 s1[] 和 s2[] 的指针
	s2[top2+1] = INF; // 由于双指针法可能取到 s2[top2+1],需要先将其设为无穷大
	for(p1; p1 <= top1; p1++)
	{
	    while(s2[p2+1] <= s1[p1] && p2 < top2) p2++;
	    ans = min({ans, abs(s2[p2] - s1[p1]), abs(s2[p2+1] - s1[p1])});
	    // s2[p2] 是 s2[] 中小于等于 s1[p1] 的最大的数,s2[p2+1] 是 s2[] 中大于 s1[p1] 的最小的数
	    // 由于 s1[] 和 s2[] 都是升序的,所以与 s1[p1] 差值最小的数一定在这两者之中 
	}
	return ans;
}

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) scanf("%lld", &a[i]), sum[i] = sum[i-1] + a[i];
	pre();
	for(int i = 1; i <= n; i++) printf("%lld\n", solve(i));
	return 0;
}

总结

这道题中,我们先思考了最暴力的做法,然后用双指针法和离散化两个技巧优化了时间复杂度。其中离散化的技巧是具有启发性的,它启示我们在数据规模较小的情况下可以先离散化,然后就可以使用一些时间复杂度更小的算法(如这道题中的桶排序)。

标签:p1,s2,Sum,top2,Subarrays,MAXN,题解,区间,s1
From: https://www.cnblogs.com/dengstar/p/solution-p9127.html

相关文章

  • 【洛谷 P1307】[NOIP2011 普及组] 数字反转 题解(字符串)
    [NOIP2011普及组]数字反转题目描述给定一个整数,请将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零(参见样例2)。输入格式一个整数。输出格式一个整数,表示反转后的新数。样例#1样例输入#1123样......
  • AGC041D-Problem Scores 题解
    题目链接luoguatcoder分析令\(k=\left\lfloor\frac{n}{2}\right\rfloor\)对于第三个条件,只需要满足\(\sum_{i=1}^{k+1}a[i]<\sum_{i=n-k+1}^{n}a[i]\)即可有一个\(trick\):构造一个单调不降或不增的序列可以转化为每次做一次前缀加操作例如本题要求构造一个单调......
  • [题解] CF1748E Yet Another Array Counting Problem
    YetAnotherArrayCountingProblem给你一个长度为\(n\)的序列和一个数\(m\),求有多少个长度为\(n\)的序列\(b\)满足:\(\foralli\in[1,n],b_i\in[1,m]\)。对于每个区间\([l,r]\),\(b\)中最大值最靠左的位置和\(a\)相同。\(n,m\le2\times10^5,n\ti......
  • [题解] P4435 [COCI2017-2018#2] ​​Garaža
    P4435[COCI2017-2018#2]Garaža给你一个长度为\(n\)的序列\(a\),单点改,查询区间\(\gcd\)不为1的子区间个数。\(n,Q\le10^5,a_i\le10^9\)。先看单次全局查询怎么做。考虑一个分治,每次我们要计算跨过分治中心\(mid\)的答案。因为这个是单调的,所以可以双指针做......
  • 【题解】P4768 [NOI2018] 归程 / Kruskal 重构树
    补补以前懒得总结的零碎东西。kruskal重构树使用条件:求无向图中两点之间所有路径的最大边权的最小值构造:依kruskal得到最小生成树从小到大考虑生成树中的边\((u,v)\)对于\((u,v)\),新建一个结点,作为重构树中\(u,v\)的父结点该结点的点权为\((u,v)\)的......
  • SPOJ1805 HISTOGRA - Largest Rectangle in a Histogram 题解
    LinkSPOJ1805HISTOGRA-LargestRectangleinaHistogramQuestion在一条水平线上有\(n\)个高为\(a_i\)的矩形,求包含于这些矩形的最大子矩形面积。Solution我们定义\(L_i\)表示有\(a_i\)这个高度的一根悬线,往左最多能平移到什么位置初始化显然,\(a_i=i\)考虑转移......
  • 题解 AT_codefestival_2016_final_f【Road of the King】
    注意到当前移动到的位置并不重要,重要的是经过的点数和\(1\)所在强连通分量大小,因此把它们放进状态里:设\(f_{i,j,k}\)表示进行\(i\)次移动,经过了\(j\)个不同的点,此时\(1\)所在的强连通分量大小为\(k\)的方案数。考察下一次移动到的点的情况:没有访问过:共有\(n-j\)......
  • UVA11282 题解
    题意简述Kelly寄出去\(n\)封邀请函,但她希望只有小于等于\(m\)个人收到他们自己的邀请函(即有至少\(n-m\)个人收到了别人的邀请函)。思路形成容易发现,这道题是一个典型的错排题,我们只需要分别求出\(n-m\)个元素到\(n\)个元素的错排即可。接下来为错排的推导,我们令\(f......
  • [题解] P4755 Beautiful Pair
    P4755BeautifulPair给你一个长度为\(n\)的序列\(a\),求有多少个区间\([l,r]\)满足\(a_l\cdota_r\le\max_{i=l}^ra_i\)。\(n\le10^5,a_i\le10^9\)。首先按最大值位置分治。记当前分治区间为\([l,r]\),分治中心为\(mid\)。那么我们现在要做的就是统计跨......
  • 【题解 P4062 & P8313】 Yazid 的新生舞会&Izbori
    [COCI2021-2022#4]Izbori题目描述Malnar先生正在竞选县长,这个县一共有\(n\)栋房屋,每栋房屋里都住着一位居民。Malnar先生知道,选举的赢家不一定是最好的候选人,而是在选举前举办的宴会最好的候选人。因此,在选举前几天,他将邀请第\(l\)至\(r(l\ler)\)栋房屋内居住的居民,为......