1 条题解
-
0
自动搬运
来自洛谷,原作者为

pocafup
路 是人走出来的搬运于
2025-08-24 22:24:23,当前版本为作者最后更新于2020-09-08 11:49:15,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
Update 10.13 博客上代码不知道哪里挂了/fad,本地改了博客上可能忘改了?被dalao指出,现已更正。
P0:题外话
讲个笑话,正解状压。
P1:基础观察
首先需要证明一个结论:如果某一个括号串在点 出发有不止一个完全匹配,那么选择结束点最小的匹配一定最佳。
因为我们规定了只有完全匹配完某个字符串才能继续匹配,所以答案一定是越早匹配完越好。
P2:题解
下文用 表示 , 表示 。
对于 的数据,暴力枚举每种可出现的排列查最大值,ans 取 max 即可
复杂度 。
对于 且随机的数据,考虑优化全排列。
发现可以使用 dfs 进行枚举,每当枚举到就退出。
复杂度 ,但在数据随机下能够过(
良心出题人)。核心枚举代码: by
https://www.luogu.com.cn/user/322075void dfs(ll pos,ll id,ll cur){ ans = max(ans,cur); if (pos >= l) return; for (ll cnt = 0;cnt < xl[id] && pos < l;pos += 1) if (x[id][cnt] == k[pos]) cnt += 1,cur += v[id]; for (ll i = 1;i <= n;i += 1) dfs(pos,i,cur); }
对于 且每个字符只用一次的数据,发现可以对每个位置进行状压。
首先预处理在每个位置时分别处理后面第一次出现 ( 和 ) 的位置,此操作用倒叙枚举能够快速的求出。
for (int i=k;i>0;i--){ nxt[0][i] = (s[i-1]=='(') ? i : nxt[0][i+1]; nxt[1][i] = (s[i-1]==')') ? i : nxt[1][i+1]; }表示目前匹配到 点,字符串存取数量为 。枚举每个串暴力查 进行转移即可,复杂度
核心转移代码:
inline void solve(int kk, int type, int pos){ int cnt = 1, val = dp[type][kk]; while(cnt<=a[pos].length()){ int now = (a[pos][cnt-1])==')'; if (!nxt[now][kk]) {ans = max(ans,val);return;}//没地方跳了 if (cnt==a[pos].length()) val+=v[pos],dp[type+(1<<(pos-1))][nxt[now][kk]+1] = max(dp[type+(1<<(pos-1))][nxt[now][kk]+1],val);//跳完了 else kk = nxt[now][kk]+1,val+=v[pos]; cnt++; } ans = max(ans,val); }另一种可行的方法为在每个位置进行 dp,在每个位置均暴力枚举字符串找匹配 (后面会具体讲 dp 思路,这里略)。
复杂度 ,人口普查。
对于 的数据,考虑暴力 dp。
令 表示在点 前能拿到的最高完美匹配,注意 不包括点 能拿到的。
转移可以枚举括号串,对每个括号串暴跳预处理出来的括号。
转移代码:
inline void jump(int kk, int pos){ int cnt = 1, val = dp[kk]; while(cnt<=a[pos].length()){ int now = (a[pos][cnt-1])==')';//转化为01 if (nxt[now][kk]==-1) {ans = max(ans,val);return;}//没地方跳了 if (cnt==a[pos].length()) dp[nxt[now][kk]+1] = max(dp[nxt[now][kk]+1],val+v[pos]);//跳完了 else kk = nxt[now][kk]+1,val+=v[pos]; cnt++; } ans = max(ans,val); }复杂度
对于 的数据,暴力可过,但有另外的解法(近似正解)
发现括号只有两种形态,考虑用 01 表示,从而想到状压。
在预处理 () 的位置后我们还可以暴力预处理每一种状态。预处理的东西跟之前意义一样,对于每个字符暴跳 ( 和 ) 即可。极限状态数 ,预处理后暴跳可以做到 处理出某一个状态在某一个点的值。
之后 dp 转移可以暴力枚举字符串转移,对于长度为 的字符串直接转移,否则暴力枚举进行转移。
复杂度
怎么好像还慢了可以考虑枚举每种长度进行状压,于是时空变为
对于 的数据,暴力复杂度错误,考虑状压。
然而直接状压 显然是个笑话,于是考虑分块思想。
发现可以将每个括号串进行分块处理。分成 块,
只需预处理长度为 的字符串即可。
对于转移,在每个点上仍然暴力枚举字符串,但我们对字符串分块跳跃。对于不足 长度的块暴跳即可。
然而发现这还是过不去发现块长使复杂度不平衡, 瓶颈在于状压的状态,因此,我们可以尝试调节块长。
复杂度 ,实测块长 都可过。
const int MAXN = 505; const int MAXK = 1e4+5; const int L = 5; int n,v[MAXN],nxt[2][MAXK],dp[MAXK],k,pre[MAXK][(1<<L)+5],num[MAXN],cor[MAXN][(MAXN/L)+1],ans; string s,a[MAXN]; int max(int a, int b){return a>b?a:b;} int solve(int pos, int num, int val){ while(true){ int now = (num>>val) & 1; if (!nxt[now][pos]) return 0; if (val==0) return nxt[now][pos]; pos = nxt[now][pos]+1;val--; } } inline int get(int pos, int f, int t){ int re = 0; for (int i=f;i<=t;i++) re += ((a[pos][i]=='(') ? 0 : 1) * (1<<(t-i)); return re; } void jump(int pos, int ptr,int cnt,int sum, int from){ if (cnt>a[pos].length()) {dp[ptr] = max(dp[ptr],dp[from]+sum);return;} while(cnt<=a[pos].length()){ int now = (a[pos][cnt-1])==')'; if (!nxt[now][ptr]) {ans = max(ans,dp[from]+sum);return;}//没地方跳了 ptr = nxt[now][ptr]+1,sum+=v[pos]; cnt++; } if (ptr!=-1)dp[ptr] = max(dp[ptr],dp[from]+sum);//跳完 ans = max(ans,dp[from]+sum); } // pos是块的编号,cor是目前块的括号序列 void Solve(int kk, int pos){ int re = 0,from = kk,cnt = 1; for (int i=1;i<=num[pos];i++){ if (pre[kk][cor[pos][i]]) { // 如果有块可以跳 kk = pre[kk][cor[pos][i]]+1; re += L*v[pos]; cnt+=L; }else break; } jump(pos,kk,cnt,re,from); } signed main(){ cin >> n >> s; k = s.length(); for (int i=1;i<=n;i++) cin >> a[i] >> v[i]; for (int i=1;i<=n;i++) { num[i] = (a[i].length()/L); for (int l=1;l<=num[i];l++) cor[i][l] = get(i,L*(l-1),L*l-1); } for (int i=k;i>0;i--){ nxt[0][i] = (s[i-1]=='(') ? i : nxt[0][i+1];// 0表示 (, 1表示 ) nxt[1][i] = (s[i-1]==')') ? i : nxt[1][i+1]; } for (int i=1;i<=k;i++) for (int j=0;j<=(1<<L)-1;j++) pre[i][j] = solve(i,j,L-1); //处理状态 for (int i=1;i<=k;i++) for (int j=1;j<=n;j++) Solve(i,j); printf("%d\n",ans); } /* 3 ((()())(()() (() 4 () 2 ()()()() 5 */审题人给出了对不足大小的块不足长度时不需暴跳的方法:
令 为长度 的块在使用 号字符串,状态为 的最早完美匹配结束点。这个长度在 solve 函数内可以求出,总复杂度不变,但暴跳时直接处理即可。
对于不是完美匹配的块,我们将 内的值变为在某个长度下最多能拿的匹配数量。为了区分结束点与长度,我们可以将这个数字设为负数,然后在累计时乘以 -1 即可。
然而不知道为啥我这么写跑得更慢了,代码不贴了,有兴趣可以找我要。
P3:可能存在的疑惑
为什么 std 在块大小不足 时要暴力转移呢?
可以发现,假设块大小为 8,用 0 表示 (,1 表示 ),则 (((((((( 的表示法为 0000000, 即 0。
然而,对于大小为 6 的小块,(((((( 的表示法同为 0,但两者代表的东西不同。故我们不能直接使用状态进行转移。
- 1
信息
- ID
- 5786
- 时间
- 1000~2200ms
- 内存
- 128MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者