Educational Codeforces Round 151 A~E

前言:F是个FFT,不想写。

A-Forbidden Integer

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

  1. 对于 iaikaix
  2. ai=n

如无法构造,输出 NO,否则输出 YES 后,输出序列长度与序列中的每一个数。
题解:
注意到顺序不重要,两个限制并不困难。考虑一种构造方案:整个序列有最少 len1 个数相同,而剩下一个数用来凑 n 即可。

我们枚举这个相同的数 i(1ik),此时凑数的那个有两个选择:nmodinmodi+i。这样两个不同的取值可以灭掉 m 的限制。当我们枚举到一个数可以满足条件,直接输出即可。否则无解。

		int tag=0;
		for(int i=1;i<=k;i++){
			int y=n%i;
			if(m==i)continue;
			if(y+i<=k&&m!=y+i){
				cout<<"Yes\n";
				cout<<n/i<<"\n"<<y+i<<" ";
				for(int j=2;j<=n/i;++j)cout<<i<<" ";
				cout<<"\n";tag=1;break;
			} 
			if(m==y)continue;
			cout<<"Yes\n";
			if(y==0){
				cout<<n/i<<"\n";
				for(int j=1;j<=n/i;++j)cout<<i<<" ";
			}
			else {
				cout<<n/i+1<<"\n";
				for(int j=1;j<=n/i;++j)cout<<i<<" ";cout<<y<<" ";
			}
			puts("");tag=1;break;
		}
		if(!tag)cout<<"No\n";

B-Come Together

给定无限大的网格图中 ABC 三点的横纵坐标,你需要求出,从 A 分别到 BC 的最短路径(曼哈顿)最大有多少格点重合。

题解:考虑将横与纵分开看,我们实际要求的是线段 (x1,x2),(x1,x3) 的交点(整点),与 (y1,y2),(y1,y3) 的交点(整点),最后注意到有一个拐点被重复统计,减掉即可。

但是在代码实现中,我们直接通过做差的方式,这样会将拐点漏掉,加上就OK。

signed main(){
	t=r();
	while(t--){
		int x1=r(),y1=r(),x2=r(),y2=r(),x3=r(),y3=r();
		int a1=0,a2=0;
		if(x1==x2&&x3==x1)a1=0;
		else {
			if(x1>min(x2,x3)&&x1<max(x2,x3))a1=0;
			else a1=min(abs(x1-x2),abs(x1-x3));
		}
		if(y1==y2&&y3==y1)a2=0;
		else{
			if(y1>min(y2,y3)&&y1<max(y2,y3))a2=0;
			else a2=min(abs(y1-y2),abs(y1-y3));
		}
		cout<<a1+a2+1<<"\n"; 
	} 
}

C-Strong Password

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

  1. 对于 1inlitiri
  2. t 不是 s 的一个子序列。

这题目NM是真的拗口,我的语文老师我对不起你啊

对于这种判断满足某种限制的存在性的问题,有经典套路:尝试构造符合条件的。

在本题中,最关键的地方是:各位相对独立,没有前后位之间的奇怪关系。题意也即要我们判断是否所有符合条件1的字符串都是它的子序列。我们可以先判断长度为 1 的,再逐步扩展到长度为 m 的子序列。那么我们要填完了第 i 位再填第 i+1 位。(没填满就说明有不是的)。设 sur 表示当前位还剩下几个数没填,visi 表示这个数在当前位是否已经填过,而 j 表示正在填第几位。

我们依次扫描原序列,设扫描到了 i,若 si[lj,rj],vis[si]=0,则说明我们必须将这个数填上,否则会被噶掉。此时 sursur1vis[si]1。若此时 sur=0,则往下一位: jj+1,sur=rjlj+1。若 j 已填满,则说明没办法了。只能输出NO退出。否则到最后也没有填满,输出YES。

        int j=0,sur=r[0]-l[0]+1;
		for(int i=0;i<10;++i)val[i]=0;
		for(int i=1;i<=n;i++){
			if(l[j]<=s[i]&&s[i]<=r[j]&&val[s[i]-'0']==0)--sur,val[s[i]-'0']=1;
			if(sur==0&&j==m-1){
				cout<<"No\n";break;	
			}
			if(!sur){
				++j;sur=r[j]-l[j]+1;for(int i=0;i<10;++i)val[i]=0;
			}
		}
		if(sur!=0||j!=m-1)cout<<"Yes\n";

D-Rating System

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

  • 如果 xk,则 xmax(k,x+ai),否则 xx+ai

你需要求出 k,使得 x 的值尽量大。

题解:容易发现,当 xk 后,无论进行什么操作,x 都不会小于 k

而,设 xi 处变为 k,在 j1,j2,jm 处再次减为 k。则可以推出 sum[i+1,j1],sum[j1+1,j2]sum[jm1+1,jm]0。进而可以得到 sum[i+1,jm]0

而我们本质上是将这几段区间和改为了0。但我们发现它虽然改了几段,但效果相同:等价于将 sum[i+1,jm] 改为了0。而这个区间有什么性质呢?

i+1 为左端点的最小子段和!因为在最小子段和中,由两端删去一个值为负的区间,剩下的那个区间的和必定不会大于0,否则删去的这个区间才是真正的最小子段和。反着推过来,这便是最小子段和。

而这个的处理呢?我们倒着做一遍最小子段和的动态规划,就可以得到以某个点作为左端点的最小子段和了。设 fi 表示 [i,n] 的最小子段和,最后实际上 x 的值为 k+sum[i+1,n]fi+1(这里显然在 i 时令 k=x 可以取到最大值)

k 等价于 sum[1,i]。所以 x 的最大值实际为总和减去最小子段和。而 k ,则等于 sum[1,i]

注意这里不需要判断 [1,i] 是否满足区间内没有负数,因为如果有,则根本不会选择这个 i

int a[505050];
int get(int k){
	int x=0;
	for(int i=1;i<=n;i++){
		if(x>=k)x=max(k,x+a[i]);
		else x=x+a[i];
	}
	return x;
}
int f[505050];
signed main(){
	ios::sync_with_stdio(false);
	cin>>t;
	while(t--){
		cin>>n;for(int i=1;i<=n;i++)cin>>a[i];
		f[n+1]=0;
		for(int i=n;i;--i){
			f[i]=min(f[i+1]+a[i],a[i]);
		}
		for(int i=1;i<=n;i++){
			a[i]+=a[i-1];
		}
		int id=-1;
		for(int i=1;i<=n;i++){
			if(id==-1||f[id]>f[i])id=i;
		}
		cout<<a[id-1]<<"\n"; 
	} 
}

E-Boxes and Balls

关键:注意到一个数组可以通过两次操作不变化,这启发我们将恰好转化为奇偶性相同的最少

gi 为将原序列变为本质不同的序列中,最小操作数为 i 的序列个数。

ans=i=1kgi[(i+k)mod2=0]

这是本题中第一个关键

考虑对于一个新序列 b1bn,它的最小操作数是?

第二个关键:由于元素只有1,0,则可以关注值相同的数的相对位置

由于1不能与1交换,0不能与0交换,所以相对而言,1的顺序是固定的。设 posi 表示原序列中第 i 个1的位置,posi 表示原序列中第 i 个1的位置,则最小操作数为 i=1cnt1|posiposi|运用相对思想,统计每个1的相对位置之差,相当于统计序列中每个位置经过了多少1,而由于1的相对位置不变,可以有:设 s 为前缀和数组,则 i=1cnt1|posiposi|=i=1n|sisi|

由于前缀和数组与原数组形成双射(一一对应),故 gi 转化为求所有合法的 s 中,i=1n|sisi|=i 的个数。

合法的 s?两个条件:

  1. s0=0,sn=cnt1
  2. 0sisi11(i[1,n])

(这里不难看出合法的 s 总共有 (ncnt1) 个)

如何求出 gi?这显然是一个动态规划问题。设 fi,j,k 表示在前 i 个数中, si=j,且 x=1i|sisi|=ks 的个数。

考虑一个朴素的转移,有:

f0,0,0=0,fi,j,k=fi1,j,k+fi1,j1,k|sij|

注意第一维在实现的时候要滚掉(此时 k|sij| 的部分需要全部归0)。时间复杂度 O(n3)。无法通过。

考虑优化。

这里注意到,将 [1,x]h 个1移动到区间外,最好的办法是移动到 x+1,x+2x+h,但即使这样,也最少要移动 h2 次。所以 j 只需要在 si±k 范围内更新即可。(否则必定不会对 k 产生贡献)。

那么,复杂度就降低到了 O(n2.5)。这里 n,k 同级。

这里有另一个可以通过的做法,但时间复杂度稍劣(慢了大概二十倍)

那就是不维护 s,而是维护 pos,这样复杂度就变成了 O(nkc),其中 c1 的个数。稍稍卡卡可以过的。

signed main(){
	read(n),read(m);
	for(int i=1;i<=n;i++){
		read(s[i]);s[i]+=s[i-1]; 
	}
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=min(s[n],s[i]+45ll);j>=max(0ll,s[i]-45ll);j--){
			for(int k=m;k>=abs(s[i]-j);k--){
				f[j][k]=f[j][k-abs(s[i]-j)];
				if(j>0)f[j][k]=(f[j][k]+f[j-1][k-abs(s[i]-j)])%p; 
			}
			for(int k=abs(s[i]-j)-1;k>=0;--k)f[j][k]=0; 
		}
	}
	int ans=0;
	for(int i=m&1;i<=m;i+=2){
		ans=(ans+f[s[n]][i])%p;
	}
	cout<<ans<<"\n";
}
posted @   spdarkle  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示