PRE 杯(#2026.1.30 Lv.1)题解

A

题面链接
相关算法 tag :分治

题意理解

意思很明确,题目也保证了括号匹配的合法性,直接分治即可。

正文

思路

不过多阐述,十分好想到分治。

实现方法

\(O(L)\) 计算括号串匹配,每个括号就是一个同样的问题,权值相乘即可

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6 + 5;
int t,n,m,pos[N];
string st;
map <string, long long> q;
void start (int l, int r, ll k){
	for (int i = l; i <= r;){
		if (st[i] == '('){
			int now = pos[i];
			if (now + 1 <= r && st[now + 1] == '_'){
				ll xsum = 0;
				now += 2;
				while (st[now] >= '0' && st[now] <= '9' && now <= r)
					xsum = xsum * 10 + st[now] - '0', now++;
					start (i + 1, pos[i] - 1, k * xsum);
			}
			else start (i + 1, pos[i] - 1, k);
			i = now;
		}
		else{
			int now = i;
			string s = ""; s += st[now];
			while (st[now + 1] >= 'a' && st[now + 1] <= 'z' && now + 1 <= r) 
			    s += st[now + 1], now++;
			if (now == r || st[now + 1] != '_'){
				q[s] += k;
				now ++;
			}
			else{
				now += 2;
				ll xsum = 0;
				while (st[now] >= '0' && st[now] <= '9' && now <= r)
					xsum = xsum * 10 + st[now] - '0', now++;
				q[s] += k * xsum;
			}
			i = now;
		}
	}
}
int main (){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> t;
	while (t--){
		q.clear();
		cin >> n >> m;
		for (int i = 1; i <= n; i++){
			cin >> st;
			stack <int> pre;
			for (int j = 0; j < st.size(); j++){
				if (st[j] == '('){
					pre.push(j);
				}
				else if (st[j] == ')'){
					pos[pre.top()] = j;
					pre.pop();
				}
			}
			start (0, st.size() - 1, 1);
		}
		for (int i = 1; i <= m; i++){
			cin >> st;
			cout << q[st] << '\n';
		}
	}
}
B

题面链接
相关算法 tag :字符串,枚举

题意理解

意思很明确,需要注意的是只有都存在才能加进集合,不是看所有的最大值的最小值,也要注意集合内的元素不一定连续。

正文

思路

\(O(n ^ 2)\) 预处理回文串,暴力的话是 \(O(n ^ 4)\) 的,加点优化预期能过 \(10\) 多个点。

\(O(n ^ 4)\) 代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5005;
char s[N];
int n,ans,f[N][N],b[N][N];
int main()
{
    ios::sync_with_stdio(0);
	cin.tie(nullptr); cout.tie(nullptr); 
	cin >> n >> s + 1;
	for (int i = 1; i <= n; i++) if (s[i] >= 'a') s[i] = s[i] - 'a' + 'A';
	for (int i = 1; i <= n; i++) f[i][i] = 1;
	for (int i = 1; i < n; i++) if (s[i] == s[i + 1]) f[i][i + 1] = 2;
	for (int len = 3; len <= n; len++){
		for (int i = 1; i <= n; i++){
		    int j = i + len - 1;
		    if (j > n) break;
			if (s[i] == s[j] && f[i + 1][j - 1] > 0) f[i][j] = len;
		}
	}
	for (int len = 1; len <= n; len++){
		for (int k = len; k >= 1; k--){
			bool now = 1;
			for (int j = 1; j + len - 1 <= n; j++){
				bool flag = 0;
				int l = j, r= j + len - 1;
				for (int p = l; p + k - 1 <= r; p++){
					if (f[p][p + k - 1] > 0){
						flag = 1;
						break;
					}
				}
				if (!flag){
					now = 0;
					break;
				}
			}
			if (now){
				ans += k;
				break;
			}
		}
		
	}
	cout << ans << '\n';
}

但这肯定过不了这题,需要优化查询到 \(O(n ^ 2)\)。考虑枚举所有子串的时候多余时间开支很大,方向就为实现 \(O(1)\) 算出任意子串的存在的回文子串的长度。

一个有意思的思路

手搓样例 AbccbA,将对应的回文子串标在坐标轴上,横轴为起点,纵轴为终点,如下:
133

显然处于直线 \(y = x + k\) 上的点的权值为 \(k + 1\)。下标 \(l\)\(r\) 的子串的回文子串对应的点处于直线 \(x = l\) ,直线 \(y = r\) 和直线 \(y = x\) 围成的三角区域内或边上,上图为 \(l = 2\)\(r = 5\) 的示例。
那问题就清晰了,如果直线 \(y = x + k\) 上没有点,那么 $\forall i \in [1,n] $ 且 \(i \in \mathbb {N}\)\(k + 1 \notin f(i)\);如果有,那么只有当 \(i\) 大于等于直线 \(y = x + k\) 上任意两点之间横坐标差值的最大值(外加第一个点与 \(0\) 比,最后一个与 \(n + 1\) 比)加上 \(k - 1\) 时,\(k + 1 \in f(i)\)

实现

想必很简单了,与上面的只有查询不同。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5005;
char s[N];
int n,f[N][N],ans,b[N];
int main()
{
    ios::sync_with_stdio(0);
	cin.tie(nullptr); cout.tie(nullptr); 
	cin >> n >> s + 1;
	for (int i = 1; i <= n; i++) if (s[i] >= 'a') s[i] = s[i] - 'a' + 'A';
	for (int i = 1; i <= n; i++) f[i][i] = 1;
	for (int i = 1; i < n; i++) if (s[i] == s[i + 1]) f[i][i + 1] = 2;
	for (int len = 3; len <= n; len++){
		for (int i = 1; i <= n; i++){
		    int j = i + len - 1;
		    if (j > n) break;
			if (s[i] == s[j] && f[i + 1][j - 1] > 0) f[i][j] = len;
		}
	}
	for (int i = 1; i <= n; i++){
		bool flag = 0;
		int l = 1,r = i,las = 0, le = 0;
		while (r <= n){
			if (f[l][r] > 0){
				le = max (le, l - las - 1);
				las = l;
				flag = 1;
			}
			l++;r++;
		}
		if (flag){
			le = max (le, l - las - 1);
			le += i;
			b[le] = max (b[le], i);
		}
	}
	for (int i = 1; i <= n; i++){
		b[i] = max (b[i - 1], b[i]);
		ans += b[i]; 
	}
	cout << ans << '\n';
}
C

题面链接
相关算法 tag :栈,模拟,贪心

题意理解

平衡力的定义就是合法括号串的定义,然后就没了 qaq

正文

下面都以合法括号串代替题中的平衡力

开始思路

首先这类问题都先想想简单的情况怎么做,因为这明显是区间匹配,考虑从单个符号(即 test \(14\))的情况入手。
那只会增,删一个的情况又如何解决呢?其实这很经典了,我们令 \(dp_i\) 表示以 \(i\) 结尾的合法括号串个数,\(match\) 表示当前位置匹配的左括号的位置,那么

\[dp_i = dp_{match - 1} + 1 \]

如果没有匹配左括号,自然 \(dp_i = 0\)。知道了这些自然就随便写代码了,用栈存储未匹配的左括号,输入右括号就匹配,删除时减去当前末尾的 \(dp\) 值,并删除对应的括号。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5000005;
ll n,opt,x,now,ans,f[N],b[N];
stack <ll> q; 
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(nullptr); cout.tie(nullptr); 
    char c;
    cin >> n;
    for (int i = 1; i <= n; i++){
    	cin >> c >> x >> opt;
    	if (c == 'L')
    	    for (int j = 1; j <= x; j++){
    		now++; 
    		q.push (now);
    		f[now] = 0;
    		b[now] = -1;
    	}
    	else if (c == 'R')
            for (int j = 1; j <= x; j++){
    		now++; 
    		if (q.size()){
    		    f[now] = f[q.top() - 1] + 1;
    		    ans += f[now];
    		    b[now] = q.top();
    		    q.pop();
    		}
    		else{
    		    f[now] = 0;
    		    b[now] = -2;
    		}
    	}
    	else if (c == 'D')
            for (int j = 1; j <= x; j++){
    		ans -= f[now];
    		if (b[now] > 0) q.push (b[now]);
    		else if (b[now] == -1) q.pop();
    		f[now] = 0;
    		now--;
    	}
    	if (opt == 1) cout << ans << '\n';
    } 
}

拿了足足 \(50\) 分!

满分思路

有了上面的铺垫距离想出正解只有一步之遥了,我们仔细分析题目的数据范围,\(x_3 \le 10\) 明显是让我们能够暴力修改,\(x_1,x_2 \le 10 ^ 9\) 明显是让我们能够区间匹配,我们就按照题目来。

分析之前的添加左括号入栈及右括号匹配

我们单个的操作太耗费时间和空间,注意到对于任意的左括号明显它的 \(dp\) 值一定是 \(0\),我么自然可以将左括号压缩成一个块来处理,记录一下它的下标起点 \(l\) 及终点 \(r\),就能表示整个左括号块,为了方便右括号匹配,我们也可以记录该块的剩余括号个数 \(res\)。对于右括号的匹配,因为我们知道对于任意的左括号它的 \(dp = 0\),那么如果当前的右括号匹配的左括号前面还是左括号,显然它的 \(dp = 1\)。一个显而易见的方法出来了:我们不需要直接存储括号序列,仅需通过存储对应的类型和下标表示,匹配时我们也不需要一个一个的匹配,可以直接匹配一个左括号块,只有将一个块的左括号全部匹配完时,\(dp\) 值才可能大于 \(1\),我们将两种情况分开即可。对于 \(dp\) 值的存储,也只需存储一个右括号块的 \(r\) 下标的 \(dp\) 值,使用哈希即可。

分析之前的删除

显然删除是不需要改逻辑的,但是我们变更了插入及匹配,删除就要随之改变,但也无非就是将修改单个括号改成了修改一个块。

实现框架

声明的量

一个左括号栈,存储下标和剩余值;一个总栈,存储下标和类型,左括号要额外存储剩余值,右括号要额外存匹配下标;一个哈希表,存储 \(dp\) 值。

L 操作

将块插入左括号栈和总栈。

R 操作

将块插入总栈,更该对应的左括号栈中的块,若失配则额外处理。

D 操作

对总栈中的块修改,更该对应的下标,或直接删块。或恢复对应的左括号块,或不需要。更改答案。

代码

建议自己先写一下再看。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 4000005;
char c,las;
ll ans,len;
int n,x,opt;
struct node1 {
	ll l,r;
	ll ml,mr;
	ll k;
	char ty;
};
struct node2 {ll l,r,res;};
node1 s[N];
node2 q[N];
int ss,qs;
unordered_map<ll,ll> pos;
int main()
{
    ios::sync_with_stdio(0);
	cin.tie(nullptr); cout.tie(nullptr); 
	cin >> n;
	for (int i = 1; i <= n; i++){
		cin >> c >> x >> opt;
		if (x == 0){
			if (opt == 1) cout << ans << '\n';
			continue;
		}
		if (c == 'L'){
			q[++qs].l = len + 1;
			q[qs].r = len + x;
			q[qs].res = x;
			
			s[++ss].l = len + 1;
			len += x;
			s[ss].r = len;
			s[ss].k = -1; s[ss].ty = 'L';
		}
		else if (c == 'R'){
			if (s[ss].k == -2 && s[ss].ty == 'R'){
		    	s[++ss].l = len + 1;
		    	len += x;
		    	s[ss].r = len;
		    	s[ss].k = -2; s[ss].ty = 'R';
		    	pos[s[ss].r] = 0;
			}
			else{
				ll res = x;
				while (qs > 0 && res > 0){
					if (q[qs].res > res){
					    s[++ss].ml = q[qs].r - res + 1;
						s[ss].mr = s[ss].ml + res - 1;
						
						q[qs].res -= res;
						q[qs].r -= res;
						
						s[ss].l = len + 1;
						len += res;
						
						s[ss].r = len;
						
						s[ss].k = -3; s[ss].ty = 'R';
						pos[s[ss].r] = 1;
						ans += s[ss].r - s[ss].l + 1;
						
						res = 0;
					}
					else{
						ll ns = q[qs].res;
						if (ns != 1){
							s[++ss].ml = q[qs].l + 1;
							s[ss].mr = q[qs].r;
						
							s[ss].l = len + 1;
							s[ss].r = len + ns - 1;
							s[ss].k = -3; s[ss].ty = 'R';
							pos[s[ss].r] = 1;
							ans += s[ss].r - s[ss].l + 1;
						}
						len += ns;
						s[++ss].l = len; s[ss].r = len;
						s[ss].ml = s[ss].mr = q[qs].l;
						s[ss].k = q[qs].l - 1; s[ss].ty = 'R';
						pos[s[ss].r] = pos[s[ss].k] + 1;
						ans += pos[s[ss].r]; 
						qs--;
						res -= ns;
					}
				}
				if (qs == 0 && res > 0){
					s[++ss].l = len + 1;
					len += res;
					s[ss].r = len;
					s[ss].k = -2; s[ss].ty = 'R';
					pos[s[ss].r] = 0;
				}
			}
		}
		else{
		    len -= x;
			ll res = x;
			while (res > 0){
				if (s[ss].k == -1){
					ll rsl_2 = q[qs].res;
					if (rsl_2 > res){
						q[qs].res -= res;
						q[qs].r -= res;
						s[ss].r -= res;
					}
					else{
						s[ss].r -= rsl_2;
						qs--;
					}
					if (s[ss].r < s[ss].l) ss--;
					res -= min (res, rsl_2);
					continue;
				}
				ll rsl = s[ss].r - s[ss].l + 1;
				if (s[ss].k == -2){
					if (rsl > res) s[ss].r -= res;
					else ss--;
					res -= min (res, rsl);
					continue;
				}
				if (s[ss].k < 0){
					if (rsl > res){
						pos[s[ss].r] = 0;
						
						q[++qs].l = s[ss].ml;
						q[qs].r = s[ss].ml + res - 1;
						q[qs].res = res;
						s[ss].ml += res;
						s[ss].r -= res;
						pos[s[ss].r] = 1;
					}
					else{
						q[++qs].l = s[ss].ml;
						q[qs].r = s[ss].mr;
						q[qs].res = q[qs].r - q[qs].l + 1;
						pos[s[ss].r] = 0;
						ss--;
					}
					
					ans -= min (res, rsl);
				}
				else{
					q[++qs].l = s[ss].ml;
					q[qs].r = s[ss].mr;
					q[qs].res = 1;
					ans -= pos[s[ss].r];
					pos[s[ss].r] = 0;
					ss--;
				}
				res -= min (res, rsl);
			}
		}
		if (opt == 1){
			cout << ans << '\n';
		}
	}
}
D

题面链接
暂时没有写 qaq

E

题面链接
暂时没有写 qaq

posted @ 2026-06-15 13:42  CGK_by_SA  阅读(2)  评论(0)    收藏  举报