【题解】Educational Codeforces Round 142(CF1792)

没有手速,再加上被 E 卡了,废掉了。

A.GamingForces

题目描述:

Monocarp 正在玩电脑游戏。他打算杀死 n 个怪兽,第 i 个的血量为 hi

Monocarp 的角色有两个魔法咒语如下,都可以以任意顺序用任意次(可以不用),每次使用相当于一次操作。

  1. 选择两个怪兽并扣一滴血。

  2. 选择一个怪兽并且直接杀死。

当一个怪兽血量为 0 时,他死了

求杀死所有怪兽的最少操作次数。

题目分析:

只有两个怪兽同为 1 点血,这个时候我们通过 1 操作直接把它们弄死才会比直接通过操作 2 直接弄死更优。

所以判一下这个就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 10005;
int a[N];
int main(){
	int T;scanf("%d",&T);
	while(T--){
		int n;scanf("%d",&n);
		for(int i=1; i<=n; i++)	scanf("%d",&a[i]);
		int cnt = 0;
		for(int i=1; i<=n; i++){
			if(a[i] == 1)	++cnt;
		}
		printf("%d\n",n - cnt / 2);
	}
	return 0;
}

B.Stand-up Comedian

题目描述:

Eve 是个单口相声新手。她的第一场表演聚集了总计 2 个观众:Alice 和 Bob。

Eve 准备了 a1+a2+a3+a4 个相声表演节目。ai 表示第 i 类相声的数目,每类的的特征如下:

  1. Alice 和 Bob 都喜欢这类相声。

  2. Alice 喜欢,Bob 不喜欢。

  3. Bob 喜欢,Alice 不喜欢。

  4. Alice 和 Bob 都不喜欢这类相声。

一开始,两位观众的心情都为 0

当一位观众听到他喜欢的相声表演时心情会1,当听到的是自己不喜欢的相声时,心情减 1

当某位观众心情严格小于 0 时,这位观众会离场。只要有一位这样的观众离场,Eve 会特别伤心并且结束整个表演。若演完了所有节目,也会结束表演。

求某种安排表演顺序的方式,使得 Eve 在结束表演前能表演的节目最多。输出最多能表演的节目数。

译者注:若演完某个节目有观众退场,这个节目也算在总数之中

0a1,a2,a3,a4108

题目分析:

好像是个大细节题,但是我一遍过就很爽[狂笑]

考虑最优的一个操作顺序肯定是:12,34

关键是中间 2,3 造成的贡献比较难算,可以显然发现我们先操作一次 2 后操作一次 3 它们的代价就抵消了,而且会多出两次节目,但是要注意如果 1 操作数量为 0 就不能相互抵消了。

所以就按照:操作 12,3 抵消、操作 2,3、操作 4,这样的顺序模拟一下即可。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int cnt[10];
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		for(int i=1; i<=4; i++)	scanf("%lld",&cnt[i]);
		int ans = 0;
		int a = cnt[1],b = cnt[1];ans += cnt[1];
		if(a == 0){
			if(cnt[2] + cnt[3] + cnt[4] > 0)	printf("1\n");
			else	printf("0\n");
			continue;
		}
		int tmp = min(cnt[2],cnt[3]);ans += 2 * tmp;
		cnt[2] -= tmp,cnt[3] -= tmp;
		if(cnt[2] > 0){
			tmp = min(cnt[2],b+1);ans += tmp;
			a += tmp,b -= tmp;
		}
		if(cnt[3] > 0){
			tmp = min(cnt[3],a+1);ans += tmp;
			a -= tmp,b += tmp;
		}
		if(a >= 0 && b >= 0){
			ans += min(min(a+1,b+1),cnt[4]);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

C.Min Max Sort

题目描述:

对于一个排列,定义一次操作为:在排列中任选两个数字,将它们中的最大值插入至队尾,最小值插入至队首。

现在给定多个排列,问每个排列最少各需多少次操作才能变得严格递增。
1n2×105

题目分析:

如果我们要进行操作,那么最后 1 操作必然是 1,n,然后倒推一下就会发现倒数第 2 次操作必然是 2,n1,然后就可以发现倒数第 i 次操作必然是 i,ni+1

所以假设我们通过 k 次操作可以使排列严格递增,也就是说值在 [k+1,nk] 是不用我们管的了,已经符合条件了,将两头的 k 个进行排完序就结束了。

这个“不用管”用形式化的语言说其实就是,设 posi 表示数 i 出现的位置,则 posk+1<posk+2<<posnk

所以可以直接一个个枚举 k 判断是否合法即可,也就是将 kn2 开始依次减 1,然后判断条件是否满足。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N],p[N];
int main(){
	int T;scanf("%d",&T);
	while(T--){
		int n;scanf("%d",&n);
		for(int i=1; i<=n; i++)	scanf("%d",&a[i]),p[a[i]] = i;
		int k = n / 2;
		while(k && p[k] < p[k+1] && p[n-k] < p[n-k+1])	--k;
		printf("%d\n",k);
	}
	return 0;
}

D.Fixed Prefix Permutations

题目描述:

对于一个排列 p,定义其美丽度 k

  • p1=1,p2=2,,pk=k,pk+1k+1

p,q 均为长度为 n 的排列,定义排列的运算 pq 为:

  • pq=rp,q,r 均为长度为 n 的排列
  • rj=qpj

现在给出 n 个长度为 m 的排列,对于每一个 ai,求 aiaj(1jn) 美丽值最大为多少,允许有 i=j

多测,T104n5×104m10

题目分析:

考虑如果就是给定了两个排列 p,q 怎么算它的美丽值。

要让 i 经过两个排列的置换后依旧是在 i 的位置,其实就是说设 posi 表示 iq 中的位置,则 pi=posi,可以将置换理解为走路这种东西然后就很好理解了。
所以其实就是求出 pos 之后两者的 lcp

那么这个题就很好解决了,可以直接对于每一个排列都求出 pos,然后插入到 trie 树里,计算答案就是枚举每一个排列然后让它在字典树上走即可。

(我竟然一开始用了 bitset 维护这个过程,差点就过了)

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+5;
int n,m,a[N][11],tot = 1,pos[N][11],ch[100 * N][11];
void insert(int x){
	int now = 1;
	for(int i=1; i<=m; i++){
		int to = pos[x][i];
		if(!ch[now][to])	ch[now][to] = ++tot;
		now = ch[now][to];
	}
}
int find(int x){
	int now = 1;
	int ans = 0;
	for(int i=1; i<=m; i++){
		if(ch[now][a[x][i]]){
			++ans,now = ch[now][a[x][i]];
		}
		else	break;
	}
	return ans;
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		for(int i=1; i<=n; i++){
			for(int j=1; j<=m; j++){
				scanf("%d",&a[i][j]);
				pos[i][a[i][j]] = j;
			}
			insert(i);
		}
		for(int i=1; i<=n; i++)	printf("%d ",find(i));
		printf("\n");
		for(int i=1; i<=tot; i++){
			for(int j=1; j<=10; j++){
				ch[i][j] = 0;
			}
		}
		tot = 1;
	}
	return 0;
}

E.Divisors and Table

题目描述:

给定一张 n×n 的表格和一个正整数 m=m1×m2,表格第 i 行第 j 列的数 ai,j=i×j

现在需要你求出 m 的每个因子 d 是否在表格中出现,若出现,则求出其出现在表格中的最小行号。

1n,m1,m2109

题目分析:

(一种乱搞做法)

1018 以内的数,因子个数最多有大约 105 个,所以显然可以将 m 的的所有因子都拿出来然后挨个判断。

要求所有的因子其实就是要对 m 质因数分解,可以直接用科技对 m 分解,也可以分别为 m1,m2 分解后合起来,这样我们就得到了 m 的所有因子。

我们要做的其实就是分解 d=a×b,且 a,bn,要求 a 尽可能小。

因为 dm 的因子,所以 a,b 也必然是 m 的因子,所以可以将因子排序之后二分。

具体来说就是二分最小的 a 使得 dan,这样只需要向后枚举一些 a 使得 ad 此时 a 就是最优的 a 了。

代码:

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

#define int long long

int t, n, m1, m2;
vector<int> a, b, c;

signed main()
{
	scanf("%lld", &t);
	while (t -- )
	{
		int cnt = 0, ans = 0;
		a.clear();
		b.clear();
		c.clear();
		
		scanf("%lld%lld%lld", &n, &m1, &m2);
		for (int i = 1; i <= m1 / i; i ++ )
		{
			if(m1 % i == 0)
			{
				a.push_back(i);
				if(m1 / i != i) a.push_back(m1 / i); 
			}
		}
		for (int i = 1; i <= m2 / i; i ++ )
		{
			if(m2 % i == 0)
			{
				b.push_back(i);
				if(m2 / i != i) b.push_back(m2 / i);
			}
		}
		sort(a.begin(), a.end());
		sort(b.begin(), b.end());
		
		for (int i = 0; i < a.size(); i ++ )
			for (int j = 0; j < b.size(); j ++ )
				c.push_back(a[i] * b[j]);
				
		sort(c.begin(), c.end());
		c.erase(unique(c.begin(), c.end()), c.end());
		
		for (int i = 0; i < c.size(); i ++ )
		{
			int l = 0, r = i;
			while (l <= r)
			{
				int mid = l + r >> 1;
				if ((c[i] + c[mid] - 1) / c[mid] <= n) r = mid - 1;
				else l = mid + 1;
			}
			for (int j = l; j < c.size(); j ++ )
			{
				if(c[j] > n) break;
				if(c[i] % c[j] == 0)
				{
					ans ^= c[j];
					cnt ++;
					break;
				}
			}
		}
		printf("%lld %lld\n", cnt, ans);
	}
}

F1.Graph Coloring

题目描述:

简单版和困难版之间的唯一区别是 n 的数据范围不同。

给出一个 n 个顶点的无向完全图。完全图是指图上任意两个顶点皆有一条边相连。你需要给图上的每条边染上红色或蓝色。

一个顶点的集合 S 被称作是红色连接的,如果对于 S 中每对顶点 (v1,v2),都存在只通过红边和 S 中顶点的路径。相仿地,一个顶点的集合 S 被称作是蓝色连接的,如果对于 S 中每对顶点 (v1,v2),都存在只通过蓝边和 S 中顶点的路径。

你需要以如下方式对图进行染色:

  • 至少有一条红边。
  • 至少有一条蓝边。
  • 对于每个大小不小于 2 的顶点集 S(也即 |S|2),S 或者是红色连接的,或者是蓝色连接的,但不能同时是红色和蓝色连接的。

计算染色方法数对 998244353 取模后的结果。

1n5×103

题目分析:

(不写 F2 了,是一个多项式科技)

题目条件就是要求对于任意一个导出子图,要么通过红色边可以联通,要么通过蓝色边可以联通,但是不能通过两种边都可以联通。

注意到一点就是:这是一个完全图,所以蓝色边和红色边互为补图,也就是如果蓝色边不连通则红色边必然联通,反之亦然。

所以我们可以直接 dpn 表示节点个数为 n 的图,不能通过蓝色边联通的合法的方案数。

转移就是考虑枚举点 n 所在的蓝色连通块大小 x

dpn=x=1n1(n1x1)dpx×(2[x=n1])dpnx

对于 n 所在的连通块这 x 个点必然满足蓝色联通也就是红色不连通,方案数等同于 dpx

对于其余的 nx 个点,它们之间蓝色联通或者红色联通都可以,所以就是系数有 2 的贡献,但当 nx=1 时,显然只有一种方案。

因为题目要求必须同时出现红边和蓝边,所以将答案减 2 即可。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e3+5;
const int MOD = 998244353;
int fac[N],inv[N],f[N];
int binom(int n,int m){
	if(n < m || n < 0 || m < 0)	return 0;
	return fac[n] * inv[m] % MOD * inv[n-m] % MOD;
}
int power(int a,int b){
	int res = 1;
	while(b){
		if(b & 1)	res = res * a % MOD;
		a = a * a % MOD;
		b >>= 1;
	}
	return res;
}
signed main(){
	int n;scanf("%lld",&n);
	fac[0] = 1;
	for(int i=1; i<=n; i++)	fac[i] = fac[i-1] * i % MOD;
	inv[n] = power(fac[n],MOD-2);
	for(int i=n-1; i>=0; i--)	inv[i] = inv[i+1] * (i+1) % MOD;
	f[1] = 1;
	for(int i=2; i<=n; i++){
		for(int j=1; j<=i-1; j++){
			f[i] = (f[i] + f[j] * f[i-j] %MOD* binom(i-1,j-1) * (2 - (j == i-1))%MOD)%MOD;
		}
	}
	printf("%lld\n",(2 * f[n] %MOD - 2 + MOD)%MOD);
	return 0;
}
posted @   linyihdfj  阅读(85)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示