C. Decreasing String
题意
给定一个初始字符串
s
1
(
1
≤
∣
s
∣
≤
1
0
6
)
s_1 \quad (1\leq |s| \leq10^6)
s1(1≤∣s∣≤106),和一个位置
p
o
s
pos
pos
假设
s
1
s_1
s1 的长度是
n
n
n,通过
s
1
s_1
s1 会形成其他
n
−
1
n-1
n−1 个字符串:
s
2
,
s
3
.
.
.
.
s
n
s_2,s_3....s_n
s2,s3....sn
每个字符串
s
i
s_i
si 都是由
s
i
−
1
s_{i-1}
si−1 删除一个字符 得到的,并且要求
s
i
s_i
si 是所有删除方案中字典序最小 的那个。
最后,将所有字符串拼接: S = s 1 + s 2 + . . . + s n S = s_1 + s_2 + ... + s_n S=s1+s2+...+sn,问 S S S 的第 p o s pos pos 个字符是什么(下标从 1 1 1 开始)
思路
我们先来考虑对于一个字符串,怎么删除才能使下一个字符串字典序最小:
如果要从 s s s 删除一个字符,使得结果字典序最小,那么删除的那个字符一定是:最左边的 i i i 满足 s i > s i + 1 s_i > s_{i+1} si>si+1,删除 i i i 即可。如果 s s s 是不递减的,那么直接删除最后一个字符
回到问题,可以发现对于给定的 p o s pos pos ,我们就可以确定 S p o s S_{pos} Spos 是那个 s i s_i si 的字符。换句话说,给定 p o s pos pos,就一定可以确定是在哪一轮删除,得到的字符串 s i s_i si 里的某个字符。可以简单地通过循环找到。
第一轮就是 s 1 s_1 s1,没有删除字符。第二轮 s 2 s_2 s2 相比 s 1 s_1 s1 删除了 1 1 1 个字符;第 i d x idx idx 轮删除了 i d x − 1 idx-1 idx−1 个字符。所以我们只需要找到这被删除的 i d x − 1 idx-1 idx−1 个字符,然后在 s i d x s_{idx} sidx 中找相应的 p o s pos pos 即可。
可以使用一个栈来模拟这个过程:从左到右遍历 s 1 s_1 s1,如果遇到一个新的字符 s i s_i si,此时栈空或者 栈顶元素不大于 s i s_i si,那么 s i s_i si 入栈,否则就有一个循环,持续删除栈顶元素,直到栈空或者栈顶元素不大于 s i s_i si 时,才使其入栈。我们只需要在删除的过程中统计个数,并给删除的下标打上标记,最后在没有被删除的字符上计数来寻找 p o s pos pos 位置的即可。
时间复杂度: O ( n ) O(n) O(n)
// Problem: C. Decreasing String
// Contest: Codeforces - Educational Codeforces Round 156 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1886/problem/C
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;
typedef long long ll;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t;
std::cin>>t;
while(t--){
std::string s;
std::cin>>s;
int n = s.size();
s = s + 'A'; //尾插一个字典序比 'a' 还小的,用于尾删
ll p;
std::cin>>p;
int len = n , idx = 1;
while(p > len){
p -= len;
++idx; //轮数
--len; //此轮长度
}
int cnt = 0;
std::stack<int> st;
int j = 0;
std::vector<bool> label(n,false);
while(cnt < idx-1 && j <= n){ //第idx轮删除了idx-1个字符
if(st.empty() || s[st.top()] <= s[j]){
st.push(j);
++j;
}
else{
label[st.top()] = true;
st.pop();
++cnt;
}
}
cnt = 0;
fore(i,0,n){
if(label[i]) continue; //已经被删除的字符
++cnt;
if(cnt == p){
std::cout<<s[i];
break;
}
}
}
return 0;
}
D. Monocarp and the Set
题意
有
n
n
n 个数字:
1
,
2
,
3...
n
1,2,3...n
1,2,3...n,还有一个初始为空的
s
e
t
set
set。每次插入一个数字进去,总共插入
n
n
n 次。
从第二次开始,每次插入数字都会写下一个字符:
- 如果新插入的数字成为了 s e t set set 里的最大值,那么写下一个 > > >
- 如果新插入的数字成为了 s e t set set 里的最小值,那么写下一个 < < <
- 否则写下 ? ? ?
最后会得到一个长度为 n − 1 n-1 n−1 的字符串 s s s,现在给定 s s s,并且还给定 q q q 次操作,每次操作:
- i c i \hspace{5pt} c ic,将 s i s_i si 替换为 c c c
先输出一个答案,表示对于原来的
s
s
s ,有多少种插入顺序可以得到
s
s
s。
然后对于每个询问,都输出相应的答案
思路
这题的关键就是倒着看 s s s 的形成过程。所有数字都插入完成后, s e t set set 一定是一个 排列,那么我们倒着遍历 s s s,如果 s i = > s_i = > si=>,相当于从 s e t set set 中删除一个最大的元素;如果 s i = < s_i = < si=<,相当于从 s e t set set 中删除一个最小的元素;如果 s i = ? s_i = ? si=?,相当于删除一个介于最小值和最大值之间的元素。
不难发现:对于删除最大值和最小值的情况,选择都是唯一的,只有删除介于之间的元素,才会有多种选择,才会对答案有贡献。 我们不妨把 s s s 看成: s 1 s 2 s 3 . . . s n − 1 s_1s_2s_3 ... s_{n-1} s1s2s3...sn−1,那么倒着遍历到 s i s_i si 时,此时 s e t set set 中还剩 i + 1 i + 1 i+1 个元素(因为第一次插入数字的时候没有形成 s 0 s_0 s0),因此如果这时候是删除介于中间的元素,选择就有: i + 1 − 2 = i − 1 i + 1 - 2 = i - 1 i+1−2=i−1 种。
对于修改的操作,可以先算出 2 − n 2-n 2−n 的答案 a n s ans ans,如果要修改某个 2 − n 2-n 2−n 的字符,就判断一下这个位置原来对答案有没有贡献,如果有的话就要先除掉,相当于乘上 i − 1 i-1 i−1 的乘法逆元;如果修改后是 ? ? ? 的话,就更新对答案的贡献即可。
对于 s 1 = ? s_1 = ? s1=? 的情况,答案一定是 0 0 0 的。这是因为不管第二次插入什么,一定会比第一次插入的元素大或者小,那么 s 1 s_1 s1 只能等于 > > > 或 < < < ,特判即可。可以预计算 1 − n 1-n 1−n 的乘法逆元以减小复杂度。
// Problem: D. Monocarp and the Set
// Contest: Codeforces - Educational Codeforces Round 156 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1886/problem/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;
typedef long long ll;
const int N=300050;
const ll mod = 998244353;
ll fast_pow(ll a,ll b,ll mod){
a %= mod;
ll res = 1;
while(b){
if(b&1) res = res * a %mod;
a = a * a %mod;
b >>= 1;
}
return res;
}
ll inv[N];
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int n,q;
std::cin>>n>>q;
std::string s;
std::cin>>s;
s = '0' + s;
inv[1] = 1;
fore(i,2,n+1) inv[i] = fast_pow(i,mod-2,mod);
ll ans = 1;
fore(i,2,n)
if(s[i] == '?')
ans = ans * (i-1) % mod;
std::cout<<(s[1] == '?' ? 0 : ans)<<endl; //s[1]单独处理
while(q--){
int p;
char c;
std::cin>>p>>c;
if(p!= 1 && s[p] == '?') ans = ans * inv[p-1] % mod;
if(p!= 1 && c == '?') ans = ans * (p-1) % mod;
s[p] = c;
std::cout<<(s[1] == '?' ? 0 : ans)<<endl;
}
return 0;
}