Educational Codeforces Round 156 C + D

C. Decreasing String

C

题意

给定一个初始字符串 s 1 ( 1 ≤ ∣ s ∣ ≤ 1 0 6 ) s_1 \quad (1\leq |s| \leq10^6) s1(1s106),和一个位置 p o s pos pos
假设 s 1 s_1 s1 的长度是 n n n,通过 s 1 s_1 s1 会形成其他 n − 1 n-1 n1 个字符串: 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} si1 删除一个字符 得到的,并且要求 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 idx1 个字符。所以我们只需要找到这被删除的 i d x − 1 idx-1 idx1 个字符,然后在 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

D

题意

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 n1 的字符串 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...sn1,那么倒着遍历到 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+12=i1 种。

对于修改的操作,可以先算出 2 − n 2-n 2n 的答案 a n s ans ans,如果要修改某个 2 − n 2-n 2n 的字符,就判断一下这个位置原来对答案有没有贡献,如果有的话就要先掉,相当于乘上 i − 1 i-1 i1 的乘法逆元;如果修改后是 ? ? ? 的话,就更新对答案的贡献即可。

对于 s 1 = ? s_1 = ? s1=? 的情况,答案一定是 0 0 0 的。这是因为不管第二次插入什么,一定会比第一次插入的元素大或者小,那么 s 1 s_1 s1 只能等于 > > > < < < ,特判即可。可以预计算 1 − n 1-n 1n 的乘法逆元以减小复杂度。

// 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; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值