Educational Codeforces Round 148 (Rated for Div. 2)

发布时间 2023-05-23 16:08:57作者: 空気力学の詩

Preface

补题,这场比较简单,E之前的都能写出来,当然D的细节挺多的WA了好几发

感觉时间好不够用啊,想补的题那么多但效率好低,可能要等暑假才能集中攻克了


A. New Palindrome

统计下出现了一次以上的字符有几种,如果大于等于两种就有解

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=55;
int t,n,c[N]; char s[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%s",s+1),n=strlen(s+1),i=0;i<26;++i) c[i]=0;
		for (i=1;i<=n;++i) ++c[s[i]-'a'];
		int cur=0; for (i=0;i<26;++i) if (c[i]>1) ++cur;
		puts(cur>1?"YES":"NO");
	}
	return 0;
}

B. Maximum Sum

直接枚举最后取了多少次最大值,然后取最小值的次数也是知道的,贡献用前缀和算一下就行

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,k,a[N]; long long pfx[N],ans;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
		for (sort(a+1,a+n+1),pfx[0]=0,i=1;i<=n;++i) pfx[i]=pfx[i-1]+a[i];
		for (ans=i=0;i<=k;++i) ans=max(ans,pfx[n-i]-pfx[2*(k-i)]);
		printf("%lld\n",ans);
	}
	return 0;
}

C. Contrast Value

不难发现如果原序列中存在三个相邻的数\(a_i,a_{i+1},a_{i+2}\)之间有\(a_i\le a_{i+1}\le a_{i+2}\)\(a_i\ge a_{i+1}\ge a_{i+2}\),则中间那个就可以被删去

具体实现的话可以记录一下相邻两数之间的大小关系,如果相等就直接忽略,否则和上一次的对比下即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int t,n,a[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
		int lst=-2,ret=1; for (i=1;i<n;++i)
		{
			int cur=a[i]==a[i+1]?0:(a[i]<a[i+1]?-1:1);
			if (cur) ret+=cur!=lst,lst=cur;
		}
		printf("%d\n",ret);
	}
	return 0;
}

D1. Red-Blue Operations (Easy Version)

首先要求最小值最大一眼二分答案\(x\),然后考虑如何check

首先很容易发现一个数被操作偶数次后值一定减小,操作奇数次一定增大

同时增大的最大情况就是只操作一次,比如只对某个位置进行一次\(+k\)操作是理论上能得到的最大值,否则由于排除掉最大的这次操作后剩下的操作为偶数,一定会让增量减小

因此就有了一个贪心的思路,给所有数从小到大排序后,给所有小于\(x\)的数先只加上最大的可能增量

然后考虑剩下的操作如何安排,不难发现把它们两个一组打包作减法是影响最小的,看下剩下的余量能否满足要求即可

但是还有各种边界情况,比如当初始时大于等于\(x\)的数有两个及以上时是一定合法的(因为偶数次操作可以被拆分为若干次奇数操作,增量一定是正的)

然后就很容易写出一个\(O(n)\)check的代码,足以通过本题(后面D2就是直接在D1的代码上优化的,因此一定要理解这里的思路)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int n,q,k,a[N],b[N],tp[N];
inline bool check(CI x)
{
	RI i; int cur=k; for (i=1;i<=n;++i) b[i]=a[i],tp[i]=0;
	for (i=1;i<=n&&cur>=1;++i) if (b[i]<x) b[i]+=cur--,tp[i]=1;
	for (i=1;i<=n;++i) if (b[i]<x) return 0;
	if (cur<1) return 1;
	int pos=0,cnt=0; for (i=1;i<=n;++i) if (!tp[i]) ++cnt,pos=i;
	if (cnt>=2) return 1;
	if (cur%2==1&&pos) b[pos]+=cur--;
	if (cur%2==1) return 0;
	for (i=1;i<=n&&cur>=1;++i) if (b[i]>x) cur-=2*(b[i]-x),b[i]=x;
	if (cur<1) return 1; return 0;
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%lld%lld",&n,&q),i=1;i<=n;++i) scanf("%lld",&a[i]);
	for (sort(a+1,a+n+1),i=1;i<=q;++i)
	{
		scanf("%lld",&k); int l=-2e9,r=2e9,mid,ret;
		while (l<=r) if (check(mid=l+r>>1)) ret=mid,l=mid+1; else r=mid-1;
		printf("%lld ",ret);
	}
	return 0;
}

D2. Red-Blue Operations (Hard Version)

有了上面的代码其实优化就非常显然了,我们发现找小于\(x\)的位置可以二分,后面算贡献可以拆分一下式子直接\(O(1)\)

唯一需要注意的是就是由于可能有相邻的数,因此在第一步检验每个数是否能做一次操作来大于等于\(x\)的过程中要找一个\(a_i-i\)最小的位置,这个可以预处理一下

然后就没啥了,总复杂度就是\(O(q\log a_i\log n)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,q,k,a[N],mipos[N],all;
inline bool check(CI x)
{
	int pos=lower_bound(a+1,a+n+1,x)-a-1,cur=k,left=0;
	if (pos>0&&a[mipos[pos]]+k-mipos[pos]+1<x) return 0;
	cur-=pos; if (cur<0) return 0;
	if (n-pos>=2) return 1;
	if (cur%2==1&&n-pos>=1) left+=cur--;
	if (cur%2==1) return 0;
	left+=all+(2*k-pos+1)*pos/2LL-n*x; cur-=2*left;
	if (cur<=0) return 1; return 0;
}
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%lld%lld",&n,&q),i=1;i<=n;++i) scanf("%lld",&a[i]),all+=a[i];
	for (sort(a+1,a+n+1),mipos[1]=1,i=2;i<=n;++i)
	if (a[i]-i<a[mipos[i-1]]-mipos[i-1]) mipos[i]=i; else mipos[i]=mipos[i-1]; 
	for (i=1;i<=q;++i)
	{
		scanf("%lld",&k); int l=-2e9,r=2e9,mid,ret;
		while (l<=r) if (check(mid=l+r>>1)) ret=mid,l=mid+1; else r=mid-1;
		printf("%lld ",ret);
	}
	return 0;
}

E. Combinatorics Problem

怎么会有这么简单的数学题的说,我感觉就是一个Div2C题的难度顶天了

处理组合数的贡献的经典方法就是差分(或者说根据组合数的递推公式),由于\(C_{x+1}^k-C_x^k=C_x^{k-1}\),因此我们发现当\(i\)增加时增量可以用之前的某些东西来表示

具体的,设\(f_{i,j}\)表示当\(k=j\)\(b_i\)的值,稍微手玩一下很容易得到转移:

\[f_{i,j}=f_{i-1,j}+f_{i-1,j-1}+[j\le 1]\cdot a_i \]

后面的那个\([j\le 1]\)其实就是\(C_1^j\),因为\(j\ge 2\)时不能直接把\(a_i\)加入\(b_i\)的贡献中

总复杂度\(O(nk)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e7+5,mod=998244353;
int n,a[N],x,y,m,k,f[N][6]; long long ans;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; scanf("%d%d%d%d%d%d",&n,&a[1],&x,&y,&m,&k);
	for (i=2;i<=n;++i) a[i]=(1LL*a[i-1]*x+y)%m;
	for (i=1;i<=n;++i) for (j=0;j<=k;++j)
	f[i][j]=(1LL*(j?f[i-1][j-1]:0)+f[i-1][j]+(j<=1?a[i]:0))%mod;
	for (i=1;i<=n;++i) ans^=1LL*f[i][k]*i;
	return printf("%lld",ans),0;
}

Postscript

感觉好久没现场打CF的比赛了,还是要跟进一下不然手生了就很难受