Luogu P3059 Concurrently Balanced Strings G 题解 [ 紫 ] [ 线性 dp ] [ 哈希 ] [ 括号序列 ]
模拟赛搬的题,dp 思路很明显,但难点就在于找到要转移的点在哪。
暴力
首先我们可以先考虑
注意一定是要在保证能找到的情况下,转移离自己最近的左括号,才能保证所有括号序列都被统计到了。
最后扫一遍把所有的
正解
上面的做法,我们发现可以拓展到全局,也就是同时有
我们考虑一个括号序列的常用 trick:把左括号看作
总和为
观察到前缀和数组每次相对前一项的变化量要么是
但是这样并不好做,因为如果
因此,我们才考虑固定左括号,去寻找右括号,并且把 dp 倒着做。
于是找出前缀和
这个我们可以通过从后往前扫描,记录下考虑序列第
如果找不到,就说明右括号在右边的哪里都可以,所以赋为最大值。
算完最大右端点后,我们对于每一列,求出其最大右端点中的最小值,这就是某一列里可能的匹配范围。
接下来考虑总和为
可得:
一个括号序列合法,必须每一行都满足这个条件,也就是说对于两个列而言,每一行的前缀和相同,它才可能合法。
所以我们对每一列哈希,存进 unordered_map,然后统计离自己最近的且在最大右端点左边的相同位即可。
最后来个 dp 就完事了,时间是
代码
代码还是比较好写的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
const ll eps=500005,mod=998244353;
int n,k,a[15][50005],f[15][50005],tot[15][110005],r[15][50005],pr[50005],y[50005];
ll hs[50005],dp[50005],ans;
unordered_map<ll,int>mp;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>k>>n;
//处理原括号序列、前缀和数组、各列的哈希值
for(int i=1;i<=k;i++)
{
for(int j=1;j<=n;j++)
{
char c;
cin>>c;
if(c=='(')a[i][j]=1;
else a[i][j]=-1;
f[i][j]=f[i][j-1]+a[i][j];
hs[j]=(hs[j]*10007%mod+f[i][j])%mod;
}
}
//统计右边最远可达的括号
memset(tot,0x3f,sizeof(tot));
memset(r,0x3f,sizeof(r));
for(int i=1;i<=k;i++)
{
for(int j=n;j>=1;j--)
{
tot[i][f[i][j]+eps]=j;
r[i][j]=tot[i][f[i][j-1]-1+eps];
}
}
//记录对于每一列而言的右边界
memset(pr,0x3f,sizeof(pr));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
pr[i]=min(pr[i],r[j][i]);
}
}
//找出相同的哈希值
for(int i=n;i>=1;i--)
{
mp[hs[i]]=i;
y[i]=mp[hs[i-1]];
}
//dp
for(int i=n;i>=1;i--)
{
if(y[i]!=0&&y[i]<=pr[i])
{
dp[i]=dp[y[i]+1]+1;
}
}
//统计答案
for(int i=1;i<=n;i++)
{
ans+=dp[i];
}
cout<<ans;
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战