首页 > 其他分享 >【题解】Educational Codeforces Round 147(CF1821)

【题解】Educational Codeforces Round 147(CF1821)

时间:2023-08-11 21:55:06浏览次数:60  
标签:147 Educational le int 题解 sum 机器人 括号 ans

自己做出来了 A-E,F 算是搞懂 GF 后第一道符合我这个菜鸡水平的实战,可惜的是根本没意识到可以 GF。

A.Matching

题目描述:

整数模板是每位均为数字或问号的字符串。

如果可以用数字替换模板中的每个问号,从而获得该正整数(严格大于 \(0\)) 的十进制表示形式,且不带任何前导零,则该正整数与整数模板匹配。

例如:
\(42\) 匹配 4?
\(1337\) 匹配 ????
\(1337\) 匹配 1?3?
\(1337\) 匹配 1337
\(3\) 不匹配 ??
\(8\) 不匹配 ???8
\(1337\) 不匹配 1?7

你将获得一个最多包含 \(5\) 个字符的整数模板。计算与其匹配的正整数(严格大于 \(0\))的数量。

\(1 \le t \le 2 \times 10^{5}\), \(t\) 为数据组数。
\(1 \le |s| \le 5\),\(|s|\) 为每组数据中字符串(整数模板)的长度。

题目分析:

除了第一个位置只可以选择 \([1,9]\) 其他的都可以选择 \([0,9]\) 所以直接乘一下就可以了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 200;
char s[N];
int main(){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%s",s+1);
		int n = strlen(s+1);
		int ans = 1;
		if(s[1] == '?')	ans = ans * 9;
		if(s[1] == '0')	ans = 0;
		for(int i=2; i<=n; i++){
			if(s[i] == '?')	ans = ans * 10;
		}
		printf("%d\n",ans);
	}
	return 0;
}

B.Sort the Subarray

题目描述:

有一个序列 \(a\),cff 排序了其中一段区间 \([l,r]\),得到了另一个新的序列。给出这两个序列,求 cff 排序的那一段区间的左右端点 \(l,r\)。输出最长的可能的区间。

题目分析:

如果有一些位置两个序列上的值不同也就是肯定排序经过这里,所以只需要找到排完序的序列的最长递增的一段且满足这一段包含值不同的位置即可。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
int a[N],b[N];
int main(){
	int T;scanf("%d",&T);
	while(T--){
		bool flag = false;
		int n;scanf("%d",&n);
		for(int i=1; i<=n; i++)	scanf("%d",&a[i]);
		for(int i=1; i<=n; i++)	scanf("%d",&b[i]);
		for(int i=1; i<=n; i++){
			if(a[i] != b[i])	flag = true;
		}
		int ans = 0,l = 0,r = 0;
		for(int i=1; i<=n; ){
			bool tag = false;
			int pos = i;
			while(b[pos+1] >= b[pos] && pos <= n)	++pos;
			for(int j=i; j<=pos; j++){
				if(a[j] != b[j])	tag = true;
			}
			if(pos - i + 1 > ans && flag == tag){
				ans = pos - i + 1;
				l = i,r = pos;
			}
			i = pos+1;
		}
		printf("%d %d\n",l,r);
		for(int i=1; i<=n; i++)	a[i] = 0,b[i] = 0;
	}
}

C.Tear It Apart

题目描述:

现有一个由小写字母组成的字符串,你将对这个字符串进行操作。每次操作你可以选择任意多个(可以只选一个)两两在字符串中不相邻的位置,把它们从字符串中删除。求至少进行多少次操作,字符串里的所有字母相同。

题目分析:

可以直接枚举最后相同的字母是什么,这样这些字母就将字符串划分成了若干段,每一段的花费次数就是 \(\lfloor \log_2{len} \rfloor + 1\),然后取一个最大值的最小值就好了。
因为注意到我们选择的位置不一定必须是 \(x,x+2,x+4,...\) 这种性质,所以可以所有段一起选,这样上面的过程就很合理了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+5;
char s[N];
int main(){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%s",s+1);
		int n = strlen(s+1);
		int ans = n + 1;
		for(char c='a'; c<='z'; c++){
			int now = 0,len = 0;
			for(int i=1; i<=n; i++){
				if(s[i] == c){
					len = max(len,now),now = 0;
				}
				else	now++;
			}
			len = max(len,now);
			if(!len)	ans = 0;
			else{
				len = ((int)log2(len)) + 1;
				ans = min(ans,len);
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

D.Black Cells

题目描述:

在一条数轴上有无穷个点,下标为 \(0, 1, 2, \ldots\),初始时每个点都是白色的。

你控制着一个机器人,初始时机器人位于坐标为 \(0\) 的那个点。

机器人有两种状态:激活状态 和 非激活状态。

当处于激活状态时,机器人所在的点都会被染成黑色。

处于非激活状态时,机器人所在的点不会被染成黑色。

初始时机器人处于非激活状态。

你可以对这个机器人进行若干次操作,操作分为三种类型。每一次操作,你可以选择如下三种操作之一执行:

  • 将机器人的位置像数轴的正方向移动一个单位(即:若当前机器人在坐标 \(x\),则执行一次该操作后机器人将移动到坐标 \(x+1\) 的那个点);
  • 激活机器人:该操作只有当机器人处于非激活状态时才能执行,执行该操作后机器人将变为 激活状态;
  • 撤销激活机器人:该操作只有当机器人处于激活状态时才能执行,执行该操作后机器人将变为 非激活状态。

有 \(n\) 个区间,第 \(i\) 个区间用 \([l_i, r_i]\) 表示。

你需要使用最少的操作次数,将至少 \(k\) 个点染成黑色,但是有一个限制,就是:这些染成黑色的点必须包含在至少一个给定的区间中,这也就是说,如果你要将坐标为 \(x\) 的那个点染成黑色,则必须保证存在一个 \(i(1 \le i \le n)\) 满足 \(l_i \le x \le r_i\)。

同时,本题也要求操作结束时机器人恢复到非激活状态(这也就意味着最少操作次数对应的最后一次操作是 撤销激活机器人)。

问:至少需要进行几次操作能够使至少 \(k\) 个点被染成黑色,且最终机器人处于非激活状态?

\(1 \le n \le 2\cdot 10^5,1 \le k \le 10^9,1 \le l_1 < l_2 < \cdots < l_n \le 10^9,1 \le r_i \le 10^9,l_i \le r_i < l_{i+1} - 1\)

题目分析:

一个显然的想法就是从前到后每次遇到一个可以选的段就选,这样看上去就很优。

但是如果这么简单会放在 EDU D 嘛,显然不是。

那么不优可能是什么情况下呢,就是我们如果选择一段必然伴随的开始一个激活和结尾的一个撤销,但是如果我们不在这里激活和撤销而直接走过去,走到下一段就只需要一个激活操作,可以手摸一下几个情况看看,比如下面两种情况(\(0\) 代表白色,\(1\) 代表黑色):\(00101100\)、\(0011011100\)。

所以仿佛只有长度为 \(1\) 的区间会导致这种情况,所以就直接分长度为 \(1\) 和长度大于等于 \(2\) 的区间处理就好了。

具体就是直接枚举长度为 \(1\) 的区间选多少个,然后在长度大于等于 \(2\) 的区间上二分找到最靠前的一个位置使得选择的黑色格子数为 \(k\),答案就很好算了。
当然也可以不用二分,就是随着长度为 \(1\) 的区间变多,我们二分的位置单调不增,所以可以双指针,但是仿佛细节有点多,不大会写。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define PII pair<int,int>
using namespace std;
const int N = 5e5+5;
const int INF = 1e17+5;
int pre[N],l[N],r[N];
vector<PII> v1,v2;
PII get(int x){
	if(v2.size() == 0 && x > 0)	return {INF,INF};
	if(pre[v2.size()-1] < x)	return {INF,INF};
	if(x == 0)	return {0,0};
	int pos = lower_bound(pre+1,pre+v2.size(),x)-pre;
	x -= pre[pos-1];
	return {v2[pos].first + x - 1,pos};
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		int n,k;scanf("%lld%lld",&n,&k);
		int sum = 0;
		for(int i=1; i<=n; i++)	scanf("%lld",&l[i]);
		for(int i=1; i<=n; i++)	scanf("%lld",&r[i]);
		for(int i=1; i<=n; i++)	sum += r[i] - l[i] + 1;
		if(sum < k){
			printf("-1\n");
			continue;
		}
		for(int i=1; i<=n; i++){
			if(l[i] == r[i])	v1.push_back({l[i],r[i]});
			else	v2.push_back({l[i],r[i]});
		}
		v1.insert(v1.begin(),{0,0});
		v2.insert(v2.begin(),{0,0});
		for(int i=1; i<(int)v2.size(); i++){
			pre[i] = pre[i-1] + (v2[i].second - v2[i].first + 1);
		}
		int ans = INF;
		for(int i=0; i<(int)v1.size(); i++){  //枚举多少个 len = 1 
			PII pos = get(k-i);  //在 len >= 2 的上面二分 
			ans = min(ans,max(pos.first,v1[i].first) + (pos.second + i) * 2);
			//走的贡献和激活的贡献 
		}
		v1.clear();v2.clear();
		printf("%lld\n",ans);
	}
	return 0;
}

E.Rearrange Brackets

题目描述:

  • 本题一个测试点内有多组测试数据
  • 对于一个匹配的括号串,定义它的权值为进行以下操作多次将它清空的最小总代价:
    • 选取两个相邻的左右括号删除,并将代价加上原右括号右边的括号数量。
  • 你可以进行 不超过 \(\bm k\) 次 以下操作,将给定的匹配括号串 \(S\) 变为另一个匹配括号串:
    • 选取 一个 括号,将它移动到串的任意位置。
  • 求最终括号串的权值最小值。
  • \(1\leq |S|,\sum |S|\leq2\times10^5\),\(0\leq k\leq5\)。

题目分析:

首先考虑如果给定一个括号串怎么找到最小的总代价,显然就是从右到左每一次选择可以选的一个然后删除即可,这个贪心显然很对吧。

所以可以发现我们其实每一次删除的都是一个合法括号匹配,也就是说我们如果把代价放到右括号右边的括号上,只有右括号有贡献,且贡献为这个括号匹配中间的合法括号匹配数,也就是说对于右括号 \(i\) 若其匹配的左括号为 \(match_i\) 则 \(i\) 的代价为 \([match_i,i]\) 中合法括号匹配数。

所以要求移动 \(k\) 个括号就可以转化为将 \(k\) 个右括号对应的贡献删掉,也就是直接将这个右括号移动到其对应的左括号的旁边。

根据贪心的想法每一次找到贡献最大的一个右括号,然后删去这个右括号就是最优的,这样直接暴力模拟复杂度就是 \(O(nk)\),可以通过。

但是这个东西是可以优化的,因为我们代价最大的右括号一定是所在的一段合法括号序列的最外的一层匹配,所以删去之后不会对任何其他右括号的贡献产生影响,所以直接将右括号按代价排序从大到小选 \(k\) 个和其对应的左括号删去即可。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
char s[N],t[N];
int sum[N],match[N],n;
void get_sum(char *s){
	stack<pair<char,int> > st;
	for(int i=1; i<=n; i++){
		if(s[i] == '(')	st.push({s[i],i});
		else{
			int pos = st.top().second;
			sum[i]++;
			match[pos] = i,match[i] = pos;
			st.pop();
		}
	}
	for(int i=1; i<=n; i++)	sum[i] += sum[i-1];  //计算 [1,i] 有多少对完美匹配的括号
}
int solve(char *s){
	get_sum(s);
	int ans = 0,pos = 0,tot = 0;
	for(int i=1; i<=n; i++){
		if(s[i] == ')')	tot += sum[i] - sum[match[i]] - 1;
		if(s[i] == ')' && sum[i] - sum[match[i]] - 1 > ans){
			ans = sum[i] - sum[match[i]] - 1;
			pos = i;
		}
	}
	for(int i=1; i<=n; i++)	sum[i] = 0;
	tot -= ans;
	int cnt = 0;
	for(int i=1; i<=n; i++){
		if(i != pos && i != match[i])	t[++cnt] = s[i];
	} 
	for(int i=1; i<=cnt; i++)	s[i] = t[i];
	n = cnt;	
	return tot;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		int k;scanf("%lld",&k);
		scanf("%s",s+1);
		n = strlen(s+1);
		for(int i=1; i<=k; i++){
			if(i == k){
				int ans = solve(s);
				printf("%lld\n",ans);
				break;
			}
			else	solve(s);
		}
		if(k == 0){
			get_sum(s);
			int ans = 0;
			for(int i=1; i<=n; i++)	if(s[i] == ')')	ans += sum[i] - sum[match[i]] - 1;
			printf("%lld\n",ans);
			for(int i=1; i<=n; i++)	sum[i] = 0;
		}
	}
	return 0;
}

F.Timber

题目描述:

在 \([1, n]\) 的区间里放 \(m\) 棵树,每棵树的高度为 \(k\)。求有多少种放置树的方法,满足:

  1. 每个树都在整点上,且每个点最多只能放一棵树。
  2. 存在一种砍倒树的方案,使得树倒了之后不会越界,也不会有某个点被超过一棵树占据。你可以自由选择树向左倒(也就是占据区间 \([x - k, x]\))或者向右倒(也就是占据区间 \([x, x + k]\))。

\(1\leq n, m, k\leq 3\times 10 ^ 5\)。

题目分析:

考虑如果给定了一个选树的局面,我们该怎么快速判断是否合法。显然我们可以让能向左倒的就向左倒,不然再向右倒,因为这样可以在后面留出尽可能多的位置方便操作。

所以为了描述这个局面,显然的 \(dp\) 就是,设 \(f[i][j]\) 表示前 \(i\) 棵树最后倒在 \(j\) (也就是倒的区间的右端点)的方案数,转移就是新加入一棵树,如下:

\[\begin{aligned} 2\times f[i][j] &\to f[i+1][x] &x\in[j+k+1,j+2k] \\ f[i][j] &\to f[i+1][x] &x\in[j+2k+1,\infty] \end{aligned} \]

第一个转移有一个 \(2\) 的系数是因为我们能找到两个位置,使得按照上述的策略一个位置向左可以倒在 \(x\) 另一个可以向右倒在 \(x\)。

如果我们知道了 \(f[i]\) 的生成函数 \(F_i\) 考虑怎么得到 \(F_{i+1}\),其实就是通过多项式乘法进行一下转移,即乘:

\[\sum_{i=k+1}^{2k} 2x^i + \sum_{i=2k+1}^{\infty} x^i \]

所以根据上述式子,我们就可以得到答案的表达式:

\[\sum_{p=0}^n [x^p] (\sum_{i=k+1}^{2k} 2x^i + \sum_{i=2k+1}^{\infty}x^i)^m \]

后面是一个多项式快速幂的形式,所以直接暴力做就可以做到 \(O(n \log n)\),但是写多项式板子这种情况怎么可能发生呢,考虑再推推更优美的形式。

\[\begin{aligned} &[x^p] (\sum_{i=k+1}^{2k} 2x^i + \sum_{i=2k+1}^{\infty}x^i)^m \\ &= [x^{p-m(k+1)}] (\sum_{i=0}^{k-1} 2x^i + \sum_{i=k}^{\infty}x^i)^m \\ &= [x^{p-m(k+1)}] (\dfrac{2-x^k}{1-x})^m \end{aligned} \]

上述化简第一步就是直接提了一个 \(x^{k+1}\),第二步就是等比数列求和。

把分子和分母拆开都是我们熟悉的封闭形式,所以直接设:

\[h(t) = [x^{tk}] (2-x^k)^m = \binom{m}{t} (-1)^{t} 2^{m-t} \\ g(t) = [x^t] (\frac{1}{1-x})^m = \binom{t+m-1}{m-1} \]

设 \(S(g,n) = \sum_{i=0}^n g(i)\),则我们的答案为 \(\sum_{ki+j \le n - m(k+1)} h(i)g(j) = \sum_{i=0}^m h(i)S(g,n - m(k+1) - ki)\)

这东西就可以线性了。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6+6;
const int MOD = 998244353;
int fac[N],inv[N],h[N],f[N];
int mod(int x){
	return x % MOD;
}
int power(int a,int b){
	int res = 1;
	while(b){
		if(b & 1)	res = mod(res * a);
		a = mod(a * a);
		b >>= 1;
	}
	return res;
}
int binom(int n,int m){
	if(n < m || n < 0 || m < 0)	return 0;
	return mod(fac[n] * mod(inv[m] * inv[n-m]));
}
signed main(){
	int n,m,k;scanf("%lld%lld%lld",&n,&m,&k);
	fac[0] = 1;
	for(int i=1; i<=n+m; i++)	fac[i] = mod(fac[i-1] * i);
	inv[n+m] = power(fac[n+m],MOD-2);
	for(int i=n+m-1; i>=0; i--)	inv[i] = mod(inv[i+1] * (i+1));
	
	for(int i=0; i<=m; i++)	f[i] = power(MOD-1,i) * power(2,m-i)%MOD * binom(m,i)%MOD;
	for(int i=0; i<=n; i++)	h[i] = mod(h[i-1] + binom(i+m-1,m-1));
	
	int ans = 0;
	for(int i=0; i<=m; i++)	
		if(n - m * (k + 1) - k * i >= 0)	ans = mod(ans + f[i] * h[n - m * (k + 1) - k * i]);
	
	printf("%lld\n",ans);
	
	return 0;
}

标签:147,Educational,le,int,题解,sum,机器人,括号,ans
From: https://www.cnblogs.com/linyihdfj/p/17623878.html

相关文章

  • Codeforces Round 874 (Div. 3) 题解
    A.MusicalPuzzle字符串\(s\)的不同的长度为\(2\)的子串个数就是答案可以用set处理B.RestoretheWeather将\(a\)数组排序后,在\(b\)数组中找到第一个大于等于\(a_i-k\)的元素与\(a_i\)对应即可可以用multiset实现(用multiset自带的lower_bound()比较好,......
  • Codeforces Round 878 (Div. 3) 题解
    A.CipherShifer从头开始扫一遍即可,扫到两个相同的表示某一个字符的解密结束B.BinaryCafe首先,我们不妨把题意转换为有多少种不同的花钱方案因为每一种咖啡就是一个二进制有\(k\)位的数字的其中一位,而对于不同的方案,其二进制位不完全相同,则每一个方案对应的花费一定不同,......
  • ARC137D Prefix XORs 题解
    这里的所有下标从\(\bm0\)开始。我们考察一下每次操作后的数列\(a\)会是什么样的。这里用\(a_i\)前面的系数\(x\)表示\(a_i\)贡献了\(x\)次,\(+\)表示异或。\[\begin{matrix}k=0&a_0&a_1&a_2&\cdots&a_{n-1}\\k=1&a_0&a_0+a_1&a_0+a_1+a_2&\cdots&......
  • Subtree 题解
    Subtree题目大意给定一颗树,你可以选出一些节点,你需要对于每个点求出在强制选这个点的情况下所有选择的点联通的方案数,对给定模数取模。思路分析对于这种求树上每一个点方案数的题目,首先考虑换根DP。强制嵌定树根为\(1\),设\(f_i\)表示在\(i\)的子树中选点,\(i\)强制选,所......
  • 国标GB28181视频云服务平台LntonGBS(源码)国标平台对接宇视SDK,多次点击录像回放出现崩溃
    LntonGBS是一款基于国标GB28181协议的视频云服务平台。通过该平台,可以实现设备接入并支持视频的实时监控直播、录像、语音对讲、云存储、告警、级联等功能。此外,LntonGBS还支持将接入的视频流进行全终端、全平台的分发,包括支持RTSP、RTMP、FLV、HLS、WebRTC等格式的视频流分发。另......
  • 题解 LuoguP3306 [SDOI2013] 随机数生成器
    题目链接:【LuoguP3306】。前置知识OI-Wiki:快速幂,扩展欧几里得算法(exgcd),BabyStep,GiantStep算法。题意很清楚,不说。分析当\(t=x\)时答案很明显为\(1\),即在第一天就可以读到。当\(t\neqx\)时当\(a=0\)时观察一下规律:\[x_1\equivx_1\pmod{p}\]\[x_2\equivb......
  • HDU 多校 Round #6 题解
    HDU多校Round#6题解\(\text{ByDaiRuiChen007}\)A.CountProblemLink题目大意求有多少个长度为\(n\),字符集大小为\(m\)的字符串有长度为\(n-k\)的周期。数据范围:\(n,m,k\le10^{18}\)。思路分析\(k=n\)时答案为\(m^n\),否则转为有长度为\(k\)的Border,答案......
  • Royal Questions题解
    题目链接RoyalQuestions-洛谷|计算机科学教育新生态(luogu.com.cn)分析每个公主会选择两个王子,考虑将每个公主所选择的两个王子连边,边权为该公主的嫁妆选择该边即为选择该公主那么结果图是什么呢?由于每个王子最多只能选择一个公主即每个点最多有1个出边(也可能没有出边......
  • 【题解】Educational Codeforces Round 148(CF1832)
    A.NewPalindrome题目描述:给你一个由小写字母组成的回文字符串,问你是否能在重排其之后构造出另一个与原串不同的回文字符串。多测,\(t\le1000,2\le|s|\le50\)题目分析:考虑其实就是前\(\lfloor\frac{n}{2}\rfloor\)个位置存在两种或以上的不同字符,因为这样直接交换对......
  • P2203 Blink 题解
    好像并没有矩阵快速幂的题解,那我来写一篇题目分析对于每两盏灯,只考虑右灯变化,分为四种情况:左灯为\(1\),右灯为\(1\),右灯变为\(0\);左灯为\(0\),右灯为\(0\),右灯不变,为\(0\);左灯为\(1\),右灯为\(0\),右灯变为\(1\);左灯为\(0\),右灯为\(1\),右灯不变,为\(1\);设第\(i\)......