文章目录
- [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;
}