sm2025 模拟赛23 (2025.10.18)

文章目录

  • [T1 前后缀排序](#T1 前后缀排序)
  • [T2 回家的路](#T2 回家的路)
  • [T3 栈模拟](#T3 栈模拟)
  • [T4 括号游戏3](#T4 括号游戏3)

T1 前后缀排序

link
思路

考虑删掉第 i i i 位会有什么影响。分以下三种:
{ a i = a i − 1 → i − 1 , i a i > a i − 1 → i , i − 1 a i < a i − 1 → i − 1 , i \begin{cases} a_i=a_{i-1} & \rightarrow i-1, i \\ a_i \gt a_{i-1} & \rightarrow i,i-1 \\ a_i \lt a_{i-1} & \rightarrow i-1,i \end{cases} ⎩ ⎨ ⎧ai=ai−1ai>ai−1ai<ai−1→i−1,i→i,i−1→i−1,i

于是我们可以确定 s i − 1 s_{i-1} si−1 和 s i s_i si 的大小关系。手动模拟样例,将连续一段字符缩成一个点,后面的一个点的排名最多会在前一个点之前,不会在前面多个点之前,于是可以用类似单调栈维护,复杂度 O ( n ) O(n) O(n) 。

代码

cpp 复制代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int stk[maxn],b[maxn];
struct NODE{ int x,cnt; }a[maxn];
string st;
int main(){
	freopen("sort.in","r",stdin);
	freopen("sort.out","w",stdout);
	int id,t; scanf("%d%d",&id,&t);
	while(t--){
		int n; scanf("%d",&n); cin>>st; int m=0,top=0,tot=0; st=" "+st;
		a[++m]={1,1},stk[++top]=1;
		for(int i=2;i<=n;i++){
			if(st[i]==st[i-1]) a[m].cnt++;
			else if(st[i]<st[i-1]){
				a[++m]={i,1};
				stk[++top]=m;
			}
			else{
				b[++tot]=stk[top--];
				a[++m]={i,1};
				stk[++top]=m;
			}
		}
		for(int i=1;i<=top;i++)
			for(int j=1;j<=a[stk[i]].cnt;j++)
				printf("%d ",a[stk[i]].x+j-1);
		for(int i=tot;i>=1;i--)
			for(int j=1;j<=a[b[i]].cnt;j++)
				printf("%d ",a[b[i]].x+j-1);
		printf("\n");
	}
	return 0;
}

T2 回家的路

link
思路

首先求出每个点的起始位置和终点位置,它只会在这个区间中移动,且它到达家后就不会再移动,此时若另外一个点经过它,那么这个点是直接跳过它的。于是一个点需走的步数相当于圆上对应的那段弧长减去这条弧所包含的弧的数量。具体直接破环为链,然后二维数点即可。

反思

没有跳出原始题目,局限于考虑每一轮的限制和变化,没有解决某个点回家路上经过的一些新到达家的点怎么做这一问题。所以要根据题目化简模型,适当跳出原始思路重新思考。

代码

cpp 复制代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;

int tree[maxn];
void Add(int x){
	while(x) tree[x]++,x-=x&(-x);
}

int n;
int Ask(int x){
	int s=0;
	while(x<=n*2) s+=tree[x],x+=x&(-x);
	return s;
}

int ans[maxn];
struct NODE{ int x,id; }b[maxn];
int main(){
	freopen("home.in","r",stdin);
	freopen("home.out","w",stdout);
	int id,t; scanf("%d%d",&id,&t);
	while(t--){
		scanf("%d",&n);
		for(int i=1,x;i<=n;i++){
			scanf("%d",&x);
			if(x>=i) b[x]={i,x},b[x+n]={i+n,x};
			else b[x+n]={i,x};
		}
		for(int i=1;i<=n+n;i++)
			if(b[i].id){
				ans[b[i].id]=i-b[i].x-Ask(b[i].x);
				Add(b[i].x);
			}
		for(int i=1;i<=n;i++)
			printf("%d ",ans[i]);
		printf("\n");
		for(int i=1;i<=n+n;i++)
			tree[i]=0,b[i]={0,0};
	}
	return 0;
}

T3 栈模拟

link
思路

对于每个询问重复模拟显然爆炸,我们希望每个数最多被遍历一次。

考虑图论。让 i → a i , t o p i \rightarrow a_{i,top} i→ai,top 。如果只连一层的话会形如基环树森林,观察到如果走到一个环那么走一圈之后又会回到起点,于是考虑消环,将环上所有点的栈顶 pop 掉,于是又进入下一层继续连边直到碰到第一个为栈为空的点就是答案。注意求答案的细节避免复杂度变劣。

反思

要转到图上考虑。

代码

cpp 复制代码
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;

int top,stk[maxn],ans[maxn];
bool vis[maxn];
stack<int>a[maxn];
int Dfs(int x){
	if(ans[x]) return ans[x];
	if(!a[x].size()) return ans[x]=x;
	if(vis[x]){
		while(x!=stk[top]){
			int u=stk[top--];
			a[u].pop();
			vis[u]=0;
		}
		vis[x]=0; a[x].pop(); top--;
		return Dfs(x);
	}
	vis[x]=1,stk[++top]=x;
	return Dfs(a[x].top());
}

int main(){
	freopen("stack.in","r",stdin);
	freopen("stack.out","w",stdout);
	int id,n; scanf("%d%d",&id,&n);
    for(int i=1,k;i<=n;i++){
        scanf("%d",&k);
        for(int j=1,x;j<=k;j++){
            scanf("%d",&x); a[i].push(x);
        }
    }
    top=0;
    for(int i=1;i<=n;i++){
        int tmp=Dfs(i);
        while(top) ans[stk[top--]]=tmp;
    }
    for(int i=1;i<=n;i++)
        printf("%d ",ans[i]);
	return 0;
}

T4 括号游戏3

link
思路

通过模拟样例和贪心猜想可以得到每次删除相邻的括号对一定是最优的。于是可以发现删除的一段是一个合法的括号序列。

出现了子问题形式,考虑 dp 。记 f i f_i fi 表示 S i , i + 1 , ... , n S_{i,i+1,\dots,n} Si,i+1,...,n 这个后缀子串中经过一些删除操作使得字典序最小的串。若保留 s i s_i si , f i ← s i + f i + 1 f_i \leftarrow s_i+f_{i+1} fi←si+fi+1 ;若删除 s i s_i si ,要保证 s i s_i si 为 ( 且 s i s_i si 的后面存在与之匹配的 ) ,记其位置为 j j j ,有 f i ← f j + 1 f_i \leftarrow f_{j+1} fi←fj+1 。这样直接转移是 O ( n 2 ) O(n^2) O(n2) 的。

考虑优化记录转移的串和计较两个串的大小。前者可以维护此时串中第一个和下一个字符的原位置,这样可以倍增跳出整个串;后者可以先倍增跳出两个串的 LCP 然后比较其下一个字符就好。这样复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) 。求 LCP 可以用哈希帮助,注意要双哈希不然单哈希对于括号序列容易发生冲突。感觉这个优化很牛啊。

反思

得到了贪心小结论然后从前往后贪心地判断每一组相邻的 () 删不删,但是发现不一定需要前缀最优,然后掉线。其实从后往前 dp 就可以了。

代码

cpp 复制代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e5+5;
const ll BS=47,BS2=71,mod=998244353,mod2=1e9+7;

int n,fa[maxn][23],nxt[maxn];
ll hs[maxn][23],hs2[maxn][23];
string st;
bool Cmp_(int x,int y){
	int x2=x;
	while(nxt[x2]!=x2) x2=nxt[x2];
	if(x2==n+1) return true;// 判断从 [x,n] 是否直接为一个合法括号序列 
	for(int i=20;i>=0;i--)
		if(fa[x][i]&&fa[y][i]&&hs[x][i]==hs[y][i]&&hs2[x][i]==hs2[y][i]) x=fa[x][i],y=fa[y][i];
	return st[x]<st[y];
}

int stk[maxn],pos[maxn];
ll pw[maxn],pw2[maxn];
int main(){
	cin>>st; n=st.size(); st=" "+st; int top=0;
	for(int i=1;i<=n;i++){
		if(st[i]=='(') stk[++top]=i;
		else if(top) nxt[stk[top--]]=i+1;
	}
	nxt[n+1]=n+1;
	pw[0]=pw2[0]=1;
	for(int i=1;i<=n;i++){
		pw[i]=pw[i-1]*BS%mod;
		pw2[i]=pw2[i-1]*BS2%mod2;
	}
	pos[n+1]=n+1;
	for(int i=n;i>=1;i--){
		pos[i]=i;
		fa[i][0]=pos[i+1],hs[i][0]=hs2[i][0]=(st[i]=='('?1:0);
		for(int j=0;j<20;j++){
			fa[i][j+1]=fa[fa[i][j]][j];
			hs[i][j+1]=(hs[fa[i][j]][j]*pw[1<<j]%mod+hs[i][j])%mod;
			hs2[i][j+1]=(hs2[fa[i][j]][j]*pw2[1<<j]%mod2+hs2[i][j])%mod2;
		}
		if(nxt[i]&&Cmp_(nxt[i],i)){
			pos[i]=pos[nxt[i]];
			for(int j=0;j<=20;j++){
				fa[i][j]=fa[nxt[i]][j];
				hs[i][j]=hs[nxt[i]][j];
				hs2[i][j]=hs2[nxt[i]][j];
			}
		}
	}
	for(int i=pos[1];i<=n;i=fa[i][0])
		cout<<st[i];
	return 0;
}
相关推荐
christ_lrs7 天前
sm2025 模拟赛12 (2025.10.7)
dp
nju_spy8 天前
力扣每日一题(二)任务安排问题 + 区间变换问题 + 排列组合数学推式子
算法·leetcode·二分查找·贪心·排列组合·容斥原理·最大堆
闻缺陷则喜何志丹9 天前
【C++贪心】P10537 [APIO2024] 九月|普及+
c++·算法·贪心·洛谷
闻缺陷则喜何志丹9 天前
【剪枝 贪心 回溯】B4093 [CSP-X2021 山东] 发送快递|普及+
c++·算法·剪枝·贪心·洛谷
闻缺陷则喜何志丹14 天前
【贪心之临项交换】P8732 [蓝桥杯 2020 国 ABC]|普及
c++·算法·蓝桥杯·贪心·洛谷
闻缺陷则喜何志丹16 天前
【中位数贪心】P6696 [BalticOI 2020] 图 (Day2)|普及+
c++·算法·贪心·洛谷·中位数贪心
Jasmine_llq2 个月前
《CF1120D Power Tree》
动态规划·dp·深度优先搜索(dfs)·广度优先搜索(bfs)·树结构处理技术·状态回溯技术
CUC-MenG2 个月前
2025杭电多校第十场 Cut Check Bit、Multiple and Factor 个人题解
数学·dp·位运算·数位dp·根号分治
Tisfy2 个月前
LeetCode 837.新 21 点:动态规划+滑动窗口
数学·算法·leetcode·动态规划·dp·滑动窗口·概率