P3059 [USACO12NOV] Concurrently Balanced Strings G 题解

前言

现在是 2023 2023 2023 7 7 7 29 29 29 日凌晨 1 1 1 47 47 47 分,我听着我歌单的歌,进入了精神极其不正常的状态(正经人谁在凌晨边听摇滚边写题啊)。

所以我会胡言几句,大家请选择性忽视。

这道题是我们欢乐赛搬的,考场上用朴素的 O ( n m 2 ) O(nm^2) O(nm2) 双指针水出了 89 89 89 分的佳绩。

考试之后因为没有暴切十分气愤啊!所以研读了一手第一篇题解,写出了这个没什么区别但是有大区别的高级重置优秀版。

原题展现

农夫约翰养了一只非常特殊的奶牛品种,以其独特的外貌而闻名,每只奶牛的皮上都有一个巨大的圆形斑点(根据奶牛的朝向不同,这可能看起来像左括号或右括号)。

一天早上,约翰把他的奶牛们分成了 K K K 行,每行 N N N 头奶牛( 1 ≤ K ≤ 10 , 1 ≤ N ≤ 50 , 000 1 \leq K \leq 10, 1 \leq N \leq 50,000 1K10,1N50,000)。由于奶牛们朝向任意方向,所以这个队列可以用 K K K 个长度为 N N N 的括号字符串 S 1 , . . . , S k S_1,..., S_k S1,...,Sk 来描述。约翰非常激动地注意到他的牛群中有一些“并发平衡”的范围,其中范围 i . . . j i...j i...j 的奶牛只有在每个字符串 S 1 , . . . , S k S_1,..., S_k S1,...,Sk 在该范围内都是平衡的情况下才能同时平衡(我们将在下面定义单个括号字符串平衡的含义)。例如,如果 K = 3 K = 3 K=3 ,我们有

  • S 1 = )()((())))(()) S_1 = \texttt{)()((())))(())} S1=)()((())))(())
  • S 2 = ()(()()()((()) S_2 = \texttt{()(()()()((())} S2=()(()()()((())
  • S 3 = )))(()()))(()) S_3 = \texttt{)))(()()))(())} S3=)))(()()))(())

那么范围 [ 3...8 ] [3...8] [3...8] 是并发平衡的,因为 S 1 [ 3...8 ] = ((())) S_1[3...8] = \texttt{((()))} S1[3...8]=((())) S 2 [ 3...8 ] = ()()() S_2[3...8] = \texttt{()()()} S2[3...8]=()()() S 3 [ 3...8 ] = (()()) S_3[3...8] = \texttt{(()())} S3[3...8]=(()()) 。范围 [ 10...13 ] [10...13] [10...13] [ 11...12 ] [11...12] [11...12] 也是并发平衡的。

给定 K K K 个长度为 N N N 的括号字符串,帮助约翰计算范围 ( i , j ) (i,j) (i,j) 的数量,使得范围 i . . . j i...j i...j K K K 个字符串中都是并发平衡的。

对于单个括号字符串的“平衡”的定义有几种方式。也许最简单的定义是括号的数量必须相等,并且对于字符串的任何前缀,左括号的数量必须至少和右括号的数量一样多。例如,以下字符串都是平衡的:

  • () \texttt{()} ()
  • (()) \texttt{(())} (())
  • ()(()()) \texttt{()(()())} ()(()())

而这些字符串则不是平衡的:

  • )( \texttt{)(} )(
  • ())( \texttt{())(} ())(
  • ((()))) \texttt{((())))} ((())))

给出 K K K 个长度为 N N N 的括号序列,问有多少个区间在 K K K 个序列中对应的子串均平衡。

我自己机翻的。

题目分析

第一次转换

括号序列的合法可以运用一个转换来判断。

把左括号变成 1 1 1,右括号变成 − 1 -1 1,然后求前缀和 s u m sum sum,合法的序列 [ l , r ] [l,r] [l,r] 当且仅当满足 s u m r = s u m l − 1 sum_r=sum_{l-1} sumr=suml1 s u m l − 1 ≤ s u m i ( i ∈ [ l , r ] ) sum_{l-1}\leq sum_{i}(i\in[l,r]) suml1sumi(i[l,r])

显然第一个条件比较好维护,第二个条件是一个类似于范围的东西,所以先处理第二个条件比较好。

那么我们怎么来找出满足这两个条件的序列呢?

我们可以枚举左端点 l l l,然后找 r r r,为什么不用 r r r 呢?我们发现判断与前缀有关与后缀无关。

第二次转换

在考虑满足第二个条件之前,我们还有一个棘手的问题:

我们还要转换一下,我们发现对于 l l l 可能有多个 r r r 是合法的,比如 ()()() \texttt{()()()} ()()() 这种括号序列。

这是怎么回事呢?我们发现 l l l 匹配了第一个答案 r 1 r_1 r1 之后,后面可能会并列其他的括号序列,只有这种情况,这个原因很简单,不证明。

我们发现对于其他的 r r r,我们完全可以去掉 [ l , r 1 ] [l,r_1] [l,r1] 这个部分,由 r 1 + 1 r_1+1 r1+1 开始向后匹配,方案数是从 l l l 匹配的方案减去一(因为你不能向前匹配 r 1 r_1 r1)。

收到启发我们可以求出 r 1 r_1 r1 然后从后向前求出 f i = f r 1 + 1 + 1 f_i=f_{r_1+1}+1 fi=fr1+1+1

第二个条件

好了,接下来考虑满足第二个条件,我们怎么求出限制范围?

我们发现说起来第一个小于本项的好像维护起来没什么头绪,但是我们仔细观察,我们会发现边界是很有特点的!

因为我们的前缀和每次不是加一就是减一,所以第一个小于本项一定为 s u m l − 1 − 1 sum_{l-1}-1 suml11 啊!

那边界不就很好求了?我们考虑维护一个我们后面 f i r x fir_x firx 表示 s u m i = x sum_i=x sumi=x 合法的一个最小的 i i i

可以倒序去做(这道题很多倒序啊),来维护。

最后就求出了一个边界了,由于这道题字符串不唯一,所以我们要对于 l l l 取所有字符串中的边界最小值。

第一个条件

第一个条件就很简单了,但是第一条不是一个告诉我们“不可以”的条件,而是让我们“怎么做”的条件,所以和第二个条件的维护略有不同。

我们求出一个最小的 r r r 使得对于每个字符串 s u m r = s u m l − 1 sum_r=sum_{l-1} sumr=suml1,说白了,我们把所有字符串的前缀和摆成二维表格,我们怎么快速判断两列的信息是否相同?

相信“快速判断”“信息相同”应该可以让你快速想到哈希,我们用哈希来存储一列的信息,然后用第二个条件的方式来做。

由于值域比较大,用 map 维护是一个不错的选择,我们就可以找到第一个和当前列完全相同的一列。

注意我们需要和第二个条件结合,如果我们维护出的 r 1 r_1 r1 超越了边界,那么一定是无解的,因为我们这个已经是最小解了,所以我们用各种小手段阻止统计即可。

求出 r 1 r_1 r1 之后保存即可,后面倒序统计答案用。

时间复杂度懒得算,大概是 O ( n m log ⁡ m ) \mathcal O(nm\log m) O(nmlogm) 的。

代码实现

注意保存 i i i 对应的 r 1 r_1 r1 是代码的 nxt 数组。

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const LL M = 15;
const LL N = 5e4 + 5;
const LL inf=1e9;
const LL mod=1e9+7;
LL n, m, sum[M][N],ans,fir[N*4],lim[N*4],nxt[N*4],hsh[N],f[N];
char s[M][N];
map<LL,LL>ma;
int main() 
{
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%s", s[i] + 1);
		for (int j = 1; j <= m; j++) {
			if (s[i][j] == '(')sum[i][j] = sum[i][j - 1] + 1;
			else sum[i][j] = sum[i][j - 1] - 1;
			hsh[j]=(hsh[j]*13+sum[i][j])%mod;
		}
	}
	memset(lim,127,sizeof(lim));
	for(int i=1;i<=n;i++)
	{
		memset(fir,127,sizeof(fir));
		for(int j=m;j>=1;j--)
		{
			fir[sum[i][j]+N]=j;
			lim[j]=min(lim[j],fir[sum[i][j-1]-1+N]);
		}
	}
	for(int i=m;i>=1;i--)
	{
		nxt[i]=ma[hsh[i-1]];
		ma[hsh[i]]=i;
	}
	for(int i=m;i>=1;i--)
	{
		if(nxt[i]&&nxt[i]<lim[i])
		{
			f[i]=f[nxt[i]+1]+1;
			ans+=f[i];
		}
	}
	printf("%lld",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: p109 [noip2004 提高组] 合并果子: 这道题目是一道经典的贪心算法题目,题目大意是给定n个果子,每个果子的重量为wi,现在需要将这n个果子合并成一个果子,每次合并需要消耗的代价为合并的两个果子的重量之和,求最小的代价。 我们可以使用贪心算法来解决这个问题,每次选择两个最小的果子进行合并,然后将合并后的果子的重量加入到集合中,重复这个过程直到只剩下一个果子为止。 这个算法的正确性可以通过反证法来证明,假设存在一种更优的合并方案,那么这个方案一定会在某一步将两个比当前选择的两个更小的果子进行合并,这样就会得到一个更小的代价,与当前选择的方案矛盾。 usaco06nov fence repair: 这道题目是一道经典的贪心算法题目,题目大意是给定n个木板,每个木板的长度为li,现在需要将这n个木板拼接成一块长度为L的木板,每次拼接需要消耗的代价为拼接的两个木板的长度之和,求最小的代价。 我们可以使用贪心算法来解决这个问题,每次选择两个最小的木板进行拼接,然后将拼接后的木板的长度加入到集合中,重复这个过程直到只剩下一个木板为止。 这个算法的正确性可以通过反证法来证明,假设存在一种更优的拼接方案,那么这个方案一定会在某一步将两个比当前选择的两个更小的木板进行拼接,这样就会得到一个更小的代价,与当前选择的方案矛盾。 ### 回答2: 题目描述: 有n个果子需要合并,合并任意两个果子需要的代价为这两个果子的重量之和。现在有一台合并机器,可以将两个果子合并成一堆并计算代价。问将n个果子合并成一堆的最小代价。 这个问题可以用贪心算法来解决,我们可以使用一个最小堆来存储所有果子的重量。每次从最小堆中取出两个最小的果子,将它们合并成为一堆,并将代价加入答案中,将新堆的重量加入最小堆中。重复以上步骤,直到最小堆中只剩下一堆为止。这样得到的代价就是最小的。 证明如下: 假设最小堆中的果子按照重量从小到大依次为a1, a2, ..., an。我们按照贪心策略,每次都将重量最小的两个果子合并成为一堆,设合并的过程为b1, b2, ..., bn-1。因此,可以发现,序列b1, b2, ..., bn-1必然是一个前缀和为a1, a2, ..., an的 Huffman 树变形。根据哈夫曼树的定义,这个树必然是最优的,能够得到的代价最小。 因此,使用贪心策略得到的答案必然是最优的,而且时间复杂度为O(n log n)。 对于[usaco06nov] fence repair g这道题,其实也可以用相同的思路来解决。将所有木板的长度存储在一个最小堆中,每次取出最小的两个木板长度进行合并,代价即为这两个木板的长度之和,并将合并后木板的长度加入最小堆中。重复以上步骤,直到最小堆中只剩下一块木板。得到的代价就是最小的。 因此,贪心算法是解决这类问题的一种高效、简单但有效的方法,可以应用于很多有贪心性质的问题中。 ### 回答3: 这两个题目都需要对操作进行模拟。 首先是合并果子。这个题目先将所有果子放进一个优先队列中。每次取出来两个果子进行合并,直到只剩下一个果子即为答案。合并的代价为两个果子重量之和。每次合并完之后再将新的果子放入优先队列中,重复上述过程即可。 再来看fence repair。这个题目需要用到贪心和并查集的思想。首先将所有板子的长度放入一个最小堆中,每次取出堆顶元素即为最短的板子,将其与其相邻的板子进行合并,合并的长度为这两块板子的长度之和。操作完之后再将新的板子长度放入最小堆中,重复上述过程直到只剩下一块板子。 关于合并操作,可以使用并查集来实现。维护每个板子所在的集合,每次操作时合并两个集合即可。 最后,需要注意的是题目中给出的整数都很大,需要使用long long来存储避免溢出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值