【题解】Educational Codeforces Round 151(CF1845)

发布时间 2023-07-24 10:31:09作者: linyihdfj

VP战报:1h 过了 A,B,C,D 然后被 E 罚坐 1h
rank:210th
题解只有 A-E

A.Forbidden Integer

题目描述:

你需要构造一个正整数序列,满足:

  1. 对于 \(i\)\(a_i\le k\)\(a_i\not=x\)
  2. \(\sum a_i=n\)

如无法构造,输出 NO,否则输出 YES 后,输出序列长度与序列中的每一个数。
多测 \(t \le 100\)\(1 \le n,k,x \le 100\)

题目分析:

我们显然可以全部使用 \(1\) 去构造,这个使用条件为 \(x \not= 1\)
或者不能用 \(1\) 就考虑使用 \(2,3\) 构造,尽可能使用 \(2\),最后如果有剩余的就将某一个 \(2\) 替换为 \(3\) 即可,使用条件自己列列就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%d",&T);
	while(T--){
		int n,k,x;scanf("%d%d%d",&n,&k,&x);
		if(x != 1){
			printf("YES\n");
			printf("%d\n",n);
			for(int i=1; i<=n; i++)	printf("1 ");
			printf("\n");
		}
		else{
			if(n == 1 || k == 1)	printf("NO\n");
			else if((n & 1) && k == 2)	printf("NO\n");
			else{
				int tmp = n / 2;
				printf("YES\n");
				printf("%d\n",tmp);
				for(int i=1; i<=tmp; i++){
					if((n & 1) && i == tmp)	printf("3 ");
					else	printf("2 ");
				}
				printf("\n");
			}
		}
	}
	return 0;
}

B.Come Together

题目描述:

给定无限大的网格图中 \(A\)\(B\)\(C\) 三点的横纵坐标,你需要求出,从 \(A\) 分别到 \(B\)\(C\) 的最短路径最大有多少格点重合。
多测 \(t \le 10^4\)\(1 \le x,y \le 10^8\)

题目分析:

距离其实就是曼哈顿距离,所以考虑两维分别拆开。
对于其中一维如果 \(B,C\) 位于 \(A\) 的异侧显然除了 \(A\) 这个点不存在重合的点,否则如果在同侧则显然重合的个数就是距离 \(A\) 较近的点与 \(A\) 的距离。
两维贡献分别加和,然后特判一下 \(A\) 这个点的贡献即可

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		int x1,x2,x3,y1,y2,y3;
		scanf("%lld%lld%lld%lld%lld%lld",&x1,&y1,&x2,&y2,&x3,&y3);
		int ans = 0;
		if((x2 >= x1 && x3 >= x1) || ((x2 <= x1 && x3 <= x1)))
			ans += min(abs(x2 - x1),abs(x3 - x1));
		if((y2 >= y1 && y3 >= y1) || ((y2 <= y1 && y3 <= y1)))
			ans += min(abs(y2 - y1),abs(y3 - y1));
		printf("%lld\n",ans + 1);
	}
	return 0;
}

C.Strong Password

题目描述:

给定一个字符串 \(s\),以及两个长度为 \(m\) 的字符串 \(l,r\)。你需要判断是否没有长度为 m 的字符串 \(t\) 满足以下要求:

  1. 对于 \(1\le i\le n\)\(l_i\le t_i\le r_i\)
  2. \(t\) 不是 \(s\) 的一个子序列。

多测 \(t \le 10^4,1 \le |s| \le 3·10^5,1 \le m \le 10\),且给定串为数字串。

题目分析:

其实本质就是让我们判断是否所有的满足 \(1\) 条件的字符串都是 \(s\) 的子序列,这个形式显然好做多了,也就是在最坏情况下依旧是。
对于子序列一个常见的想法就是贪心匹配,也就是每次找到最靠前的一个出现的位置匹配,对于每一位我们可以贪心的这样匹配然后找到匹配出来的最靠后的位置作为我们这一位最劣的位置,然后从这个最劣的位置开始下一位的匹配。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+5;
bool vis[N];
char s[N],l[N],r[N];
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%d",&T);
	while(T--){
		scanf("%s",s+1);int m;scanf("%d",&m);
		scanf("%s%s",l+1,r+1);
		int n = strlen(s+1);
		int pos = 1,sz = 0;
		for(int i=1; i<=n; i++){
			if(l[pos] <= s[i] && s[i] <= r[pos] && !vis[s[i]-'0']){
				vis[s[i]-'0'] = true,sz++;
			}	
			if(sz == r[pos] - l[pos] + 1){
				++pos;sz = 0;
				if(pos == m + 1)	break;
				for(int j=0; j<=10; j++)	vis[j] = false;
			}
		}
		if(pos == m + 1)	printf("NO\n");
		else	printf("YES\n");
		for(int j=0; j<=10; j++)	vis[j] = false;
	}
	return 0;
}

D.Rating System

题目描述:

给定一个长度为 \(n\) 数列 \(a\),保证每项都不为 \(0\)。初始时 \(x=0\),然后对于 \(1\le i\le n\),按顺序进行如下操作:

  • 如果 \(x\ge k\),则 \(x\rightarrow \max(k, x+a_i)\),否则 \(x\rightarrow x+a_i\)

你需要求出 \(k\),使得 \(x\) 的值尽量大。
多测 \(t \le 10^4,\sum_n \le 3·10^5,-10^9 \le a_i \le 10^9,a_i \not= 0\)

题目描述:

我们考虑随意指定一个 \(k\) 然后把 \(x\) 变化的折线图画出来,观察是否具有什么性质:

其实就是可以将整个过程分为三段,其中第一段和最后一段的贡献不受任何影响,第二段的贡献被强制变成了 \(0\)
为了使得最后的值最大,我们显然要选择一个最小的子段和让它的贡献变成 \(0\),至于选择的 \(k\) 显然就是一个前缀和。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 6;
const int INF = 1e18+5;
int a[N];
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		int n;scanf("%lld",&n);
		for(int i=1; i<=n; i++)	scanf("%lld",&a[i]);
		int nw = 0,l = 0;
		int ans1 = INF,ans2 = 0;
		for(int i=1; i<=n; i++){
			nw += a[i];
			if(nw > 0) l = 0,nw = 0;
			else{
				if(!l) l = i;
				if(nw < ans1) ans1 = nw,ans2 = l-1;
			}
		}
		int sum = 0;
		for(int i=1; i<=ans2; i++)	sum += a[i];
		printf("%lld\n",sum);
	}
	return 0;
}

E.Boxes and Balls

题目描述:

给定一个长度为 \(n\),由 \(0,1\) 构成的数组 \(a\)。你可以对他进行以下操作:

  • 交换数列中一对相邻的 \(0\)\(1\)

问在恰好 \(k\) 次操作后,可以产生多少种本质不同的数组。
\(2 \le n \le 1500,1 \le k \le 1500,a_i \in \{0,1\}\)

题目分析:

考虑什么样的最终状态是合法的。
可以发现的一点就是我们如果操作 \(t\) 次可以到达某个状态,操作 \(t+2\) 次也一定可以,因为我们可以重复操作某一个位置,所以为了不重其实我们只关系到达这个状态的最小操作次数。
那么考虑如果给定了一个最终状态 \(b\) 怎么计算它的最小操作次数,只用在意 \(1\) 的位置变化,因为确定了 \(1\) 就全部确定了。
可以对于每个位置计算跨过这个位置的次数,也就是设 \(s_i = \sum_{j=1}^i a_j,s'_i = \sum_{j=1}^i b_j\),也就是前 \(i\) 个有多少个 \(1\),那么最小操作次数就是 \(\sum |s_i - s'_i|\)
这样我们发现就可以将问题转化为一个看上去就很可做的形式,求有多少个序列 \(s\) 满足 \(s'_0 = 0,s'_n = n,|s'_{i} - s'_{i-1}| \le 1,\sum |s_i-s'_i| \le k,\sum |s_i - s'_i| \equiv k \mod 2\)
其实发现如果想要完整地表示这个状态要记录的值只有 \(s'_i\)\(\sum |s_i - s'_i|\),所以就考虑设 \(f_{i,j,p}\) 表示前 \(i\) 个数 \(s'_i = j\)\(\sum_{j=1}^i |s_j - s'_j| = p\) 的方案数。转移也是简单的,就是枚举 \(s'_{i-1}\)

\[f[i-1][j][p-|s_i - j|] + f[i-1][j-1][p-|s_i - j|] \to f[i][j][p] \]

下面默认 \(n,k\) 同阶,则复杂度就是 \(O(n^3)\) 需要稍微优化一下。
\(|s_i - s'_i|\) 的本质就是跨过 \(i\)\(1\) 的数量,那么考虑对于 \(x\) 个数想要跨过 \(i\) 需要的操作数是 \(O(x^2)\) 的,也就是说其实 \(|s_i - s'_i|\) 不会超过 \(O(\sqrt{k})\),那么只需要枚举这些值就好了。
复杂度被优化到了 \(O(n^2\sqrt{n})\)

代码:

(感觉直接看的话循环边界会很抽象,不行就自己写写然后自己优化)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1505;
const int MOD = 1e9+7;
int f[2][N][N],a[N],s[N];
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,k;scanf("%lld%lld",&n,&k);
	for(int i=1; i<=n; i++){
		scanf("%lld",&a[i]);
		s[i] = s[i-1] + a[i];
	}
	f[0][0][0] = 1;
	for(int i=1; i<=n; i++){
		int now = i & 1,lst = now ^ 1;
		memset(f[now],0,sizeof(f[now]));
		for(int j=max(0ll,s[i]-50); j<=min(s[n],s[i]+50); j++){
			for(int p=abs(s[i]-j); p<=k; p++){
				if(j != 0)	f[now][j][p] = f[lst][j-1][p-abs(s[i]-j)];
				f[now][j][p] = (f[now][j][p] + f[lst][j][p-abs(s[i]-j)])%MOD;
			}
		}
	}
	int ans = 0;
	while(k >= 0){
		ans = (ans + f[n&1][s[n]][k])%MOD;
		k -= 2;
	}
	printf("%lld\n",ans);
	return 0;
}