Codeforces Round 962 (Div. 3)

前言

        势必要拿下的一场比赛,最后结果也算如愿。

        Standings:300

        重新回到蓝名了,也完成了之前 “ 早日在比赛切掉 6 题 ” 的期望。

        题目链接:Dashboard - Codeforces Round 962 (Div. 3) - Codeforces

A. Legs

        第一次在第一分钟就切题 ~~

        题意:

        Farmer John 的农场只有鸡和牛,他数出动物一共有 n 条腿,问最少有多少只动物。

        思路:

        因为牛有四条腿,尽可能多地让牛的腿去填满 n 。

#include<cstdio>
#include<cstring>
using namespace std;

int T,n;

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d",&n);
		printf("%d\n",(n + 2) / 4);
	}
	return 0;
}

B. Scale

        题意:

        给一个 01 矩阵,你需要把这个矩阵分成若干个 k * k 的子矩阵,每一个子矩阵都缩成一个单个的块,其值等于原来子矩阵内每个格的值(题目保证每个子矩阵的所有格的数字都一样)。

        例如:

        0 0 0 1 1 1

        0 0 0 1 1 1

        0 0 0 1 1 1

        1 1 1 0 0 0

        1 1 1 0 0 0

        1 1 1 0 0 0

        k = 3 时  ->

        0 1

        1 0

        思路:

        题目说得很清楚了,好像不知道该怎么讲。。。

#include<cstdio>
#include<cstring>
using namespace std;

#define N 1005

int T,n,k,a[N][N];
char s[N];

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d%d",&n,&k);
		for (int i = 1;i <= n;++ i)
		{
			scanf(" %s",s + 1);
			for (int j = 1;j <= n;++ j) a[i][j] = s[j] - '0';
		}
		for (int i = 1;i <= n / k;++ i)
		{
			for (int j = 1;j <= n / k;++ j) printf("%d",a[(i - 1) * k + 1][(j - 1) * k + 1]);
			printf("\n");
		}
	}
	return 0;
}

C. Sort

        题意:

        给两个长度为 n 的字符串 a 和 b (由小写拉丁字母组成),你需要回答 q 个询问。

        询问之间是独立的,每个询问给你一个区间范围 [l,r] ,每次操作你可以任意选择一个 i ,令a_i = x ,x 可以是任意字符,求最少进行多少次操作可以让 sorted( a[l..r] ) = sorted( b[l..r] ) 。

        思路:

        因为对于 b 在 a 中每个缺失的字符都需要 1 次操作来填补,而我们并不关心到底是通过变化哪个 a 中的字符来满足条件。这样问题就转化为了在区间 [l,r] 内,每种字符 a 比 b 少多少个 (a 比 b 多的话记为 0),然后算总和。

        由于字符种类只有 26 种,给每种字符记录出现次数的前缀和即可算区间出现数目。

        比赛的时候没想那么多直接开敲,打了树状数组,虽然麻烦了点且多耗了点时间,但好在还是一遍过了。

#include<cstdio>
#include<cstring>
using namespace std;

#define N 200005

int T,n,q,t[30],now[30],tra[30][N],trb[30][N],ans;
char a[N],b[N];

int lowbit(int x) { return x & (-x) ; }

void adda(int x,int y)
{
	for (int i = y;i <= n;i += lowbit(i))
		++ tra[x][i];
	return;
}

void addb(int x,int y)
{
	for (int i = y;i <= n;i += lowbit(i))
		++ trb[x][i];
	return;
}

int aska(int x,int y)
{
	int tmp = 0;
	for (int i = y; i ;i -= lowbit(i))
		tmp += tra[x][i];
	return tmp;
}

int askb(int x,int y)
{
	int tmp = 0;
	for (int i = y; i ;i -= lowbit(i))
		tmp += trb[x][i];
	return tmp;
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d%d",&n,&q);
		scanf("%s%s",a + 1,b + 1);
		for (int i = 1;i <= 26;++ i)
			for (int j = 1;j <= n;++ j) tra[i][j] = trb[i][j] = 0;
		for (int i = 1;i <= n;++ i)
		{
			int na = a[i] - 'a' + 1;
			int nb = b[i] - 'a' + 1;
			adda(na,i),addb(nb,i);
		}
		while (q --)
		{
			int l,r;
			scanf("%d%d",&l,&r),ans = 0;
			for (int i = 1;i <= 26;++ i)
			{
				int ta = aska(i,r) - aska(i,l - 1);
				int tb = askb(i,r) - askb(i,l - 1);
				ans += (ta < tb) ? (tb - ta) : 0;
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}

D. Fun

        题意:

        给两个正整数 n 和 x ( 1 <= n,x <= 10^6 ),计算满足条件的三元组的数目。

        1. ab + ac + bc <= n

        2. a + b + c <= x

        3. a,b,c 为正整数

        注意(1,1,2)和(1,2,1)是不同的三元组。

        思路:

        注意到 n 比较大,直接枚举 a,b,c 肯定会超时。

        条件 1 是乘法再相加,从这里入手:假设我们枚举 a ,那么根据 ab < ab + ac + bc <= n ,b的取值就是 \frac{n}{a} 的量级,这时候我们枚举 b < \frac{n}{a} ,根据调和级数复杂度就是 O(n log n)的。枚举完 a 和 b 之后,根据条件 2 可以确定 c 的取值范围:0 < c <= x - a - b 。我们在做的时候设定 a <= b <= c ,最后根据情况乘上对应的组合数即可。

        最终的时间复杂度:O(n log n)

#include<cstdio>
#include<cstring>
using namespace std;

int T,n,x;
long long ans;

int min(int x,int y) { return x < y ? x : y ; }

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d%d",&n,&x),ans = 0ll;
		for (int a = 1;a <= x;++ a)
			for (int b = a;b <= (n / a);++ b)
			{
				int r = min(x - (a + b),(n - a * b) / (a + b));
				int l = b;
				if(l > r) break;
				if(a == b) ans += 1ll + 3ll * (r - l);
				else ans += 3ll + 6ll * (r - l);
			}
		printf("%lld\n",ans);
	}
	return 0;
}

E. Decode

        题意:

        给一个长度为 n 的 01 串 s ,对每一对(l,r),统计 0 和 1 个数相等的子区间 [x,y] 数目。

        思路:

        显然不能直接做,我们考虑计算每一个 0 和 1 个数相等的子区间的贡献。

        记一个 0 为 +1 ,一个 1 为 -1 ,满足条件的区间 [x,y] 即为区间和为 0 的区间,即第 y 位和第 x - 1 位的前缀和相等,那么这段区间的贡献就是 x * (n - y + 1) 。但是这么做还是太慢,我们发现可以统计不同前缀和的值对应的区间左端点 x 的和,这样每加入一个新的 y 作为右端点时,y 的贡献就变成了 (n - y + 1) * \sum x,这样就把时间优化到线性了。

        记得取模 !!!

#include<cstdio>
#include<cstring>
using namespace std;

#define N 200005
#define mod 1000000007

int T,n,now[N * 5],pre[N * 5],cnt[N * 5];
long long ans;
char s[N];

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%s",s + 1),n = strlen(s + 1),pre[0] = N;
		for (int i = -n;i <= n;++ i) now[i + N] = -1,cnt[i + N] = 0;
		now[N] = 0,ans = 0ll,cnt[N] = 1;
		for (int i = 1,x,y,tmp;i <= n;++ i)
		{
			x = (s[i] == '0') ? 1 : -1 ;
			pre[i] = pre[i - 1] + x;
			if(now[pre[i]] != -1) ans = (ans + 1ll * cnt[pre[i]] * (n - i + 1)) % mod;
			now[pre[i]] = i;
			cnt[pre[i]] = (cnt[pre[i]] + i + 1) % mod;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

F. Bomb

        题意:

        给两个序列 a 和 b ,你的初始得分为 0 ,每一轮你可以选择一个 i 并得到 a_i 的收益,然后令 a_i = max(a_i - b_i , 0) 。你只有 k 轮操作机会,求最大得分。

        思路:

        首先贪心地想,我们每轮都需要选择当前场上最大的那个数,但暴力做显然会超时。我们可以二分最后选择的那个数(即选择的最小的数)的大小,计算把这个数选上要花多少轮,不断地靠近 k 即可。注意二分值可能对应多个相同的数,不一定会全部选完,写得时候注意细节。

#include<cstdio>
#include<cstring>
using namespace std;

#define N 200005

int T,n,a[N],b[N];
long long k,ans;

long long max(long long x,long long y) { return x > y ? x : y ; }

long long check(int x)
{
	long long tmp = 0ll;
	for (int i = 1;i <= n;++ i)
		if(a[i] >= x)
			tmp += 1ll + (a[i] - x) / b[i];
	return tmp;
}

long long count(int x)
{
	long long tmp = 0ll;
	for (int i = 1;i <= n;++ i)
	{
		if(a[i] < x) continue;
		if(a[i] < b[i])
		{
			tmp += 1ll * a[i];
			continue;
		}
		int m = 1 + (a[i] - x) / b[i];
		int fst = a[i] - (m - 1) * b[i];
		int lst = a[i];
		tmp += 1ll * (fst + lst) * m / 2ll;
	}
	return tmp;
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d%d",&n,&k),ans = 0ll;
		long long l = 0ll;
		long long r = 0ll;
		for (int i = 1;i <= n;++ i) scanf("%d",&a[i]),r = max(r,1ll * a[i]);
		for (int i = 1;i <= n;++ i) scanf("%d",&b[i]);
		while (l <= r)
		{
			int mid = (l + r) >> 1;
			if(check(mid) <= k) r = mid - 1,ans = max(ans,count(mid));
			else if(check(mid + 1) < k && check(mid) > k) r = mid - 1,ans = max(ans,count(mid + 1) + 1ll * mid * (k - check(mid + 1)));
			else l = mid + 1;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

G. Penacony

        题意:

        n 个房子 n 条道路连成一个环,所有道路都是双向的但开始时都没有维修,维修之后道路才可以通行。给出 m 对关系 a,b(a < b),表示 a,b 之间一定要有一条通路,求最少维修的道路数量。

        思路:

        对于每一对关系,只有两种方式可以连通,一种是 a - > b ,另一种是 b - > n - > 1 - > a 。因此假设某条道路是不维修的,那么所有关系的连通方式都是确定唯一的。

        我们先假设初始状态:每对关系都是 a - > b 连通,用线段树标记路径。之后枚举不维修的道路,把曾经覆盖了这条路的记录去掉,再标记这条路相对的那条路径,查询 n 条道路中有多少条是被覆盖了的即可。

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

#define N 200005

int T,n,m,a[N],b[N],vis[N],ans;
vector<int> c[N];

struct Tree
{
	int val,tag;
}tr[N << 2];

void build(int k,int l,int r)
{
	tr[k].tag = tr[k].val = 0;
	if(l == r) return;
	int mid = (l + r) >> 1;
	build(k << 1,l,mid),build((k << 1) | 1,mid + 1,r);
	return;
}

void update(int k,int l,int r,int x,int y,int w)
{
	if(x > y) return;
	if(l >= x && r <= y)
	{
		tr[k].tag += w;
		if(tr[k].tag) tr[k].val = r - l + 1;
		else tr[k].val = tr[k << 1].val + tr[(k << 1) | 1].val;
		if(l == r && !tr[k].tag) tr[k].val = 0;
		return;
	}
	int mid = (l + r) >> 1;
	if(x <= mid) update(k << 1,l,mid,x,y,w);
	if(y > mid) update((k << 1) | 1,mid + 1,r,x,y,w);
	if(tr[k].tag) tr[k].val = r - l + 1;
	else tr[k].val = tr[k << 1].val + tr[(k << 1) | 1].val;
	return;
}

int main()
{
	scanf("%d",&T);
	while (T --)
	{
		scanf("%d%d",&n,&m),ans = n;
		for (int i = 0;i <= n;++ i) c[i].clear();
		build(1,1,n);
		for (int i = 1;i <= m;++ i)
			scanf("%d%d",&a[i],&b[i]),vis[i] = 0,c[a[i]].push_back(i),c[b[i] - 1].push_back(i);
		for (int i = 1;i <= m;++ i) update(1,1,n,a[i],b[i] - 1,1);
		for (int i = 1;i <= n;++ i)
		{
			for (auto j : c[i])
			{
				if(!vis[j])
				{
					update(1,1,n,a[j],b[j] - 1,-1);
					update(1,1,n,b[j],n,1);
					update(1,1,n,1,a[j] - 1,1);
					vis[j] = 1;
				}
				else
				{
					update(1,1,n,a[j],b[j] - 1,1);
					update(1,1,n,b[j],n,-1);
					update(1,1,n,1,a[j] - 1,-1);
				}
			}
			ans = min(ans,tr[1].val);
		}
		printf("%d\n",ans);
	}
	return 0;
}

总结

        这场比赛算是目前来说打得最满意的一场了,美中不足的是 C 题做复杂了浪费了点时间,E 题因为没看到取模也罚时了挺久,在解题速度上还有很大的提升空间,现在的愿景是早日 AK 一场比赛,任重而道远hhh 。打了一两个月 cf ,思维题上有了提升,但想想也只是填补了一些基础不牢的问题,还有很多算法和数据结构太久没碰了十分生疏,有待接着填坑。

  • 37
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Codeforces Round 894 (Div. 3) 是一个Codeforces举办的比赛,是第894轮的Div. 3级别比赛。它包含了一系列题目,其中包括题目E. Kolya and Movie Theatre。 根据题目描述,E. Kolya and Movie Theatre问题要求我们给定两个字符串,通过三种操作来让字符串a等于字符串b。这三种操作分别为:交换a中相同位置的字符、交换a中对称位置的字符、交换b中对称位置的字符。我们需要先进行一次预处理,替换a中的字符,然后进行上述三种操作,最终得到a等于b的结果。我们需要计算预处理操作的次数。 根据引用的讨论,当且仅当b[i]==b[n-i-1]时,如果a[i]!=a[n-i-1],需要进行一次操作;否则不需要操作。所以我们可以遍历字符串b的前半部分,判断对应位置的字符是否与后半部分对称,并统计需要进行操作的次数。 以上就是Codeforces Round 894 (Div. 3)的简要说明和题目E. Kolya and Movie Theatre的要求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Codeforces Round #498 (Div. 3) (A+B+C+D+E+F)](https://blog.csdn.net/qq_46030630/article/details/108804114)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Codeforces Round 894 (Div. 3)A~E题解](https://blog.csdn.net/gyeolhada/article/details/132491891)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值