Codeforces 873 简要题解

本文详细介绍了Codeforces 873竞赛中的六道题目解题思路,涉及模拟、前缀和、动态规划、线段树、贪心算法等多种算法,包括A题的时间花费优化、B题的01串子串分析、C题的矩阵贡献最大值、D题的归并排序递归次数、E题的奖牌分配策略以及F题的子串出现计数问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

A题

传送门
题意:
一个人要做 n n n件事,时间花费分别为 a 1 , a 2 , . . . , a n , a 1 ≤ a 2 ≤ a 3 ≤ . . . ≤ a n a_1,a_2,...,a_n,a_1\le a_2\le a_3\le...\le a_n a1,a2,...,an,a1a2a3...an
现在可以把至多 k k k件事分别花 x x x时间做完,问做完的最小总花费。


思路:按照题意模拟
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1; 
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc(); 
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int N=205;
int n,k,x,a[N];
int main(){
	n=read(),k=read(),x=read();
	for(ri i=1;i<=n;++i)a[i]=read();
	int ans=0;
	int pos=n-k;
	for(ri i=n,cnt=1;i&&cnt<=k;--i,++cnt){
		if(a[i]<=x){pos=i;break;}
		ans+=x;
	}
	while(pos)ans+=a[pos--];
	cout<<ans;
	return 0;
}

B题

题意:
给一个长度为 n n n 01 01 01串,问其最长的长度为偶数子串的 01 01 01数相等的长度。


思路:
0 0 0对应权值为 − 1 -1 1 1 1 1对应权值为 1 1 1,这样转化成求权值为 0 0 0的长度为偶数的子区间数。
这个可以用前缀和来看,如果前缀和相等说明中间夹的一段合法。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1; 
inline char gc(){
	return getchar();
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc(); 
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int N=1e5+5;
char s[N];
int n,a[N];
int pos[N<<1][2];
int main(){
	n=read();
	scanf("%s",s+1);
	memset(pos,-1,sizeof(pos));
	pos[100000][0]=0;
	int ans=0;
	for(ri sum=0,i=1;i<=n;++i){
		sum+=s[i]=='0'?-1:1;
		if(~pos[sum+100000][i&1])ans=max(ans,i-pos[sum+100000][i&1]);
		else pos[sum+100000][i&1]=i;
	}
	cout<<ans;
	return 0;
}

C题

传送门
题意:给一个 n ∗ m n*m nm 01 01 01矩阵和一个数 k k k,现在对每一列以如下方式计算贡献:

  1. 若这一列没有 1 1 1,直接跳过。
  2. 如果有 1 1 1,找到最靠上的一个 1 1 1,然后贡献为从它开始向下连续至多 k k k个格子中 1 1 1的数量。

现在允许你把一些 1 1 1变成 0 0 0,问最后获得的贡献的最大值已经要使得贡献最大需要删掉格子的最小数量。


思路:对于每一列处理一个前缀和然后随便更新一下答案。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1; 
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc(); 
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int N=105;
int n,m,k,a[N][N],sum[N][N],f[N][N];
int main(){
	n=read(),m=read(),k=read();
	for(ri i=1;i<=n;++i){
		for(ri j=1;j<=m;++j){
			a[i][j]=read(),sum[i][j]=a[i][j]+sum[i-1][j];
		}
	}
	int ans1=0,ans2=0;
	for(ri j=1;j<=m;++j){
		if(!sum[n][j])continue;
		int pre=0,pos=0;
		for(ri i=n;i;--i){
			if(!a[i][j])continue;
			f[i][j]=sum[min(n,i+k-1)][j]-sum[i-1][j];
			if(f[i][j]>=pre)pre=f[i][j],pos=i;
		}
		ans1+=pre,ans2+=sum[pos-1][j];
	}
	cout<<ans1<<' '<<ans2<<'\n';
	return 0;
}

D题

传送门
题意:
现在有一个归并排序函数 m e r g e s o r t ( l , r ) mergesort(l,r) mergesort(l,r)表示将 [ l , r ) [l,r) [l,r)位置的数给归并排序,如果已经有序会直接返回,否则会调用 m e r g e s o r t ( l , m i d ) mergesort(l,mid) mergesort(l,mid) m e r g e s o r t ( m i d , r ) mergesort(mid,r) mergesort(mid,r),现在给你 n n n k k k,要求构造出一个序列使得恰好调用 k k k次这个函数能够使得排序完成,无解输出 − 1 -1 1


思路:
首先将这个函数递归到的区间都看成结点,这就构成了一棵线段树,然后经过对题意的理解可以发现一个显然的结论: k k k必须是一个奇数否则一定无解。
证明可以考虑数学归纳法。
然后我们一直递归左右区间,每次向下走都会产生 2 2 2的贡献,走不动了就返回即可。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1; 
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc(); 
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int N=2e5+5;
int n,k,siz[N<<2],ans[N];
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (l+r>>1)
inline void build(int p,int l,int r){
	siz[p]=1;
	if(r-l<2)return;
	build(lc,l,mid),build(rc,mid,r);
	siz[p]=siz[lc]+1+siz[rc];
}
inline void solve(int p,int l,int r){
	if(!k||r-l<2)return;
	for(ri det=0;det+l<mid&&det+mid<r;++det)swap(ans[l+det],ans[mid+det]);
	k-=2;
	solve(lc,l,mid);
	if(!k)return;
	solve(rc,mid,r);
}
#undef lc
#undef rc
#undef mid
int main(){
	n=read(),k=read();
	if(!(k&1))return puts("-1"),0;
	build(1,0,n);
	if(k>siz[1])return puts("-1"),0;
	if(k==1){
		for(ri i=1;i<=n;++i)cout<<i<<' ';
		return 0;
	}
	for(ri i=0;i<n;++i)ans[i]=i+1;
	--k;
	solve(1,0,n);
	for(ri i=0;i<n;++i)cout<<ans[i]<<' ';
	return 0;
}

E题

传送门
题意:有 n ( n ≤ 3000 ) n(n\le3000) n(n3000)个人参与比赛,每个人都有一个解题数,现在要决定拿金牌的人数 c n t 1 cnt1 cnt1,拿银牌的人数 c n t 2 cnt_2 cnt2,拿铜牌的人数 c n t 3 cnt_3 cnt3,各自对应一个解题数区间 [ d 1 , c 1 ] , [ d 2 , c 2 ] , [ d 3 , c 3 ] [d1,c1],[d2,c2],[d3,c3] [d1,c1],[d2,c2],[d3,c3]
现在要求:

  1. d 1 − c 2 d1-c2 d1c2最大化
  2. 满足第一点之后 d 2 − c 3 d2-c3 d2c3最大化
  3. 满足前两点之后 d 3 − 胸 牌 第 一 名 解 题 数 d3-胸牌第一名解题数 d3最大化
  4. 对于任意的x,y(1<=x,y<=3),cntx<=2*cnty

现在需要构造出一种合法的方案 1 , 2 , 3 , − 1 1,2,3,-1 1,2,3,1表示金牌,银牌,铜牌和胸牌。


思路:
考虑枚举金牌线和银牌线。这样铜牌线的可行范围是一段连续区间,我们在差分数组上建立一个 s t st st表取个极值即可。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1;
inline char gc(){
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc();
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int N=3005;
int diff[N],n,st[N][15],Log[N],p1,p2,p3,ans[N];
struct Node{
	int a,id;
	friend inline bool operator<(const Node&a,const Node&b){return a.a<b.a;}
}a[N];
int main(){
	n=read();
	for(ri i=1;i<=n;++i)a[i]=(Node){read(),i};
	sort(a+1,a+n+1);
	for(ri i=0;i<n;++i)diff[i]=a[i+1].a-a[i].a,st[i][0]=i;
	p1=-1;
	for(ri i=2;i<=n;++i)Log[i]=Log[i>>1]+1;
	for(ri j=1;j<=12;++j)for(ri i=0;i<=n-(1<<j);++i)st[i][j]=diff[st[i][j-1]]>=diff[st[i+(1<<(j-1))][j-1]]?st[i][j-1]:st[i+(1<<(j-1))][j-1];
	for(ri t1,t2,t3,l1,l2,i=(n+1)>>1;i<n;++i){
		if(~p1&&diff[i]<diff[p1])continue;
		t1=i,l1=n-i;
		for(ri t,L,R,j=1;j<n;++j){
			l2=i-j,t2=j;
			if(2*l2<l1||2*l1<l2||j*2<l2||j*2<l1)continue;
			L=(max(l1,l2)+1)/2,R=min(l1,l2)*2;
			if(L>R)continue;
			R=max(0,j-R),L=max(0,j-L),swap(L,R);
			t=Log[R-L+1];
			t3=(diff[st[L][t]]>=diff[st[R-(1<<t)+1][t]]?st[L][t]:st[R-(1<<t)+1][t]);
			if(~p1){
				if(diff[p1]>diff[t1])continue;
				if(diff[p1]==diff[t1]&&diff[p2]>diff[t2])continue;
				if(diff[p1]==diff[t1]&&diff[p2]==diff[t2]&&diff[p3]>diff[t3])continue;
			}
			p1=t1,p2=t2,p3=t3;
		}
	}
	for(ri i=1;i<=p3;++i)ans[a[i].id]=-1;
	for(ri i=p3+1;i<=p2;++i)ans[a[i].id]=3;
	for(ri i=p2+1;i<=p1;++i)ans[a[i].id]=2;
	for(ri i=p1+1;i<=n;++i)ans[a[i].id]=1;
	for(ri i=1;i<=n;++i)cout<<ans[i]<<' ';
	return 0;
} 

F题

传送门
题意:给一个字符 t t t,上面有一些禁止位置,对于每一个子串 s s s f ( s ) f(s) f(s)表示 s s s t t t中出现且结尾位置未被禁止的合法位置数,现在求 ∣ s ∣ ∗ f ( s ) |s|*f(s) sf(s)最大值。


思路: s a m sam sam模板题,直接套板子即可。
代码:

#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int rlen=1<<18|1; 
inline char gc(){
	return getchar();
	static char buf[rlen],*ib,*ob;
	(ib==ob)&&(ob=(ib=buf)+fread(buf,1,rlen,stdin));
	return ib==ob?-1:*ib++;
}
inline int read(){
	int ans=0;
	char ch=gc(); 
	while(!isdigit(ch))ch=gc();
	while(isdigit(ch))ans=((ans<<2)+ans<<1)+(ch^48),ch=gc();
	return ans;
}
const int N=2e5+5;
typedef long long ll;
char s[N],t[N];
int n;
namespace sam{
	int len[N<<1],link[N<<1],son[N<<1][26],siz[N<<1],last=1,tot=1;
	inline void expand(int x,int val){
		int p=last,np=++tot;
		siz[last=np]=val,len[np]=len[p]+1;
		while(p&&!son[p][x])son[p][x]=np,p=link[p];
		if(!p){link[np]=1;return;}
		int q=son[p][x],nq;
		if(len[q]==len[p]+1){link[np]=q;return;}
		len[nq=++tot]=len[p]+1,memcpy(son[nq],son[q],sizeof(son[q])),link[nq]=link[q];
		link[q]=link[np]=nq;
		while(p&&son[p][x]==q)son[p][x]=nq,p=link[p];
	}
	inline void solve(){
		static int cnt[N<<1],rk[N<<1];
		ll ans=0;
		for(ri i=1;i<=tot;++i)++cnt[len[i]];
		for(ri i=1;i<=tot;++i)cnt[i]+=cnt[i-1];
		for(ri i=1;i<=tot;++i)rk[cnt[len[i]]--]=i;
		for(ri i=tot;i^1;--i)siz[link[rk[i]]]+=siz[rk[i]];
		for(ri i=1;i<=tot;++i)ans=max(ans,(ll)len[i]*siz[i]);
		cout<<ans;
	}
}
int main(){
	n=read();
	scanf("%s%s",s+1,t+1);
	for(ri i=1;i<=n;++i)sam::expand(s[i]-'a',t[i]=='1'?0:1);
	sam::solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值