Educational Codeforces Round 151 (Rated for Div. 2)

Preface

期末考终于结束了,终于可以回来写题了

这场是刚考完最后一门的那个晚上打的,因为太久没有写代码了所以不是很熟练而且脑子也不太灵光,只能堪堪写到D题而且手速感人

上次CF第二个号因为Rating被roll了导致从紫名掉下来了,这场就把它又打上去了,再这样后面兴许要用第三个号打了

由于过两天还有军训(虽然我昨天骑车把手脚全摔了不知道能不能去伤病连),所以在一轮集训前可能也没法很集中地写题,等一轮集训的时候应该会加大更新频率吧


A. Forbidden Integer

细节比较多的A题,首先当\(n=1\or k=1\)则一定无解

否则此时\(n\ge 2\and k\ge 2\),考虑如果\(x\ne 1\),则直接用\(n\)\(1\)即可

否则考虑\(x=1\)的情形,此时若\(2|n\)则用\(\frac{n}{2}\)\(2\)即可

否则此时\(2\nmid n\),因此\(n>3\),我们只要判断如果\(k\ge 3\)则可以用一个\(3\)\(\frac{n-3}{2}\)\(2\),否则就无解

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,n,k,x;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; scanf("%d%d%d",&n,&k,&x);
		if (n==1||k==1) { puts("NO"); continue; }
		if (x!=1)
		{
			puts("YES"); printf("%d\n",n);
			for (i=1;i<=n;++i) printf("%d%c",1," \n"[i==n]);
			continue;
		}
		if (n%2==0)
		{
			puts("YES"); printf("%d\n",n/2);
			for (i=1;i<=n/2;++i) printf("%d%c",2," \n"[i==n/2]);
			continue;
		}
		if (k<3) puts("NO"); else
		{
			puts("YES"); printf("%d\n",1+(n-3)/2); printf("%d ",3);
			for (i=1;i<=(n-3)/2;++i) printf("%d%c",2," \n"[i==(n-3)/2]);
		}
	}
	return 0;
}

B. Come Together

首先考虑用一个最小的矩形\((x_L,y_L),(x_R,y_R)\)\(B,C\)两点恰好包含,然后分类讨论

如果\(A\)点在矩形内部的话那么只有起点是公共点,答案就是\(1\)

否则就分\(A\)点相对于矩形的位置讨论下即可,要么到角上再分开要么到某条边再分开,具体看代码吧

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,xA,yA,xB,yB,xC,yC;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d%d%d%d%d%d",&xA,&yA,&xB,&yB,&xC,&yC);
		int xL=min(xB,xC),xR=max(xB,xC),yL=min(yB,yC),yR=max(yB,yC);
		if (xL<=xA&&xA<=xR&&yL<=yA&&yA<=yR) { puts("1"); continue; }
		if (xA<=xL&&yA<=yL) printf("%d\n",(xL-xA)+(yL-yA)+1); else
		if (xA<=xL&&yA>=yR) printf("%d\n",(xL-xA)+(yA-yR)+1); else
		if (xA>=xR&&yA>=yR) printf("%d\n",(xA-xR)+(yA-yR)+1); else
		if (xA>=xR&&yA<=yL) printf("%d\n",(xA-xR)+(yL-yA)+1); else
		if (xA<xL) printf("%d\n",xL-xA+1); else
		if (xA>xR) printf("%d\n",xA-xR+1); else
		if (yA<yL) printf("%d\n",yL-yA+1); else
		if (yA>yR) printf("%d\n",yA-yR+1);
	}
	return 0;
}

C. Strong Password

刚开始写DP方程不取\(\max\)而是直接赋值WA了一发给我逗乐了,还检查了10min真是苦路西

首先很容易想出一个DP的做法,设\(f_{i,j}\)表示处理了密码的前\(i\)位,其中第\(i\)位填的是数字\(j\),此时将密码的前\(i\)位在原串中做匹配时最靠前的匹配位置

转移的话考虑枚举上一位选的数字\(k\),我们只要找出从原串的第\(f_{i-1,k}\)个位置之后第一个数字\(j\)的位置然后转移即可

这个东西可以通过倒着枚举原串然后预处理出来,最后只要看是否有状态的值大于\(|S|\)即可

总复杂度\(O(|S|\sum+m\sum^2)\),其中\(\sum=10\)为字符集大小

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=300005,M=15;
int t,n,m,nxt[N][12],f[M][12],bkt[12]; char s[N],l[M],r[M];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j,k; for (scanf("%s",s+1),n=strlen(s+1),i=0;i<10;++i) bkt[i]=n+1;
		for (i=n;i>=1;--i) for (bkt[s[i]-'0']=i,j=0;j<10;++j) nxt[i][j]=bkt[j];
		for (scanf("%d%s%s",&m,l+1,r+1),i=l[1]-'0';i<=r[1]-'0';++i) f[1][i]=nxt[1][i];
		for (i=2;i<=m;++i) for (j=l[i]-'0';j<=r[i]-'0';++j)
		for (f[i][j]=0,k=l[i-1]-'0';k<=r[i-1]-'0';++k)
		if (f[i-1][k]>=n) f[i][j]=max(f[i][j],n+1); else f[i][j]=max(f[i][j],nxt[f[i-1][k]+1][j]);
		bool flag=0; for (i=l[m]-'0';i<=r[m]-'0';++i)
		if (f[m][i]>n) flag=1; puts(flag?"YES":"NO");
	}
	return 0;
}

D. Rating System

其实做法早就想出来了,但就是懒得去写线段树上二分,结果YY了其它的方法好久无果最后去写一下就写完了

一眼答案可能有单峰性质,然后写了个暴力跑了下样例发现确实是,结果交了一发惨遭WA掉,后面仔细一想好像确实不对劲,遂跑路去写正解

首先不难发现设\(pfx_i\)\(a_i\)的前缀和,则可能的\(k\)的取值只可能在\(pfx_0\sim pfx_n\)之中,并且最后的答案一定是\(pfx_i+suf_{i+1}\)的形式,其中\(suf_i\)表示从\(i\)开始向后累加Rating,如果出现\(<0\)的情形就重置到\(0\),最后的累加和

正着做感觉不好处理,我们可以考虑逆推,不妨设当前处理的是\(suf_i\),我们已经求出了\(i\)为开头的前缀和数组\(pfx'\)

\(pfx'_i\sim pfx'_n\)\(\ge 0\),则此时\(suf_i=pfx'_n\),否则我们可以找出第一个使得\(pfx'_j<0\)\(j\),则可以直接递推出\(suf_i=suf_{j+1}\)

实现的时候可以用线段树维护\(pfx'\),然后查询的话就是经典的线段树上二分即可,后面想了下好像可以直接单调栈+二分来做,不过没再细想了

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=300005;
int t,n,a[N],pfx[N],suf[N],stk[N],top,ans,k,dlt;
class Segment_Tree
{
	private:
		struct segment
		{
			int mi,tag;
		}node[N<<2];
		#define MI(x) node[x].mi
		#define T(x) node[x].tag
		#define TN CI now=1,CI l=0,CI r=n
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void pushup(CI now)
		{
			MI(now)=min(MI(now<<1),MI(now<<1|1));
		}
		inline void apply(CI now,CI mv)
		{
			MI(now)+=mv; T(now)+=mv;
		}
		inline void pushdown(CI now)
		{
			if (T(now)) apply(now<<1,T(now)),apply(now<<1|1,T(now)),T(now)=0;
		}
	public:
		inline void build(TN)
		{
			T(now)=MI(now)=0; if (l==r) return;
			int mid=l+r>>1; build(LS); build(RS);
		}
		inline void modify(CI beg,CI end,CI mv,TN)
		{
			if (beg<=l&&r<=end) return apply(now,mv); int mid=l+r>>1; pushdown(now);
			if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS); pushup(now);
		}
		inline int query(TN)
		{
			if (MI(now)>=0) return n+1; if (l==r) return l; int mid=l+r>>1; pushdown(now);
			if (MI(now<<1)<0) return query(LS); return query(RS);
		}
		#undef M
		#undef T
		#undef TN
		#undef LS
		#undef RS
}SEG;
signed main()
{
	
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		RI i; for (scanf("%lld",&n),i=1;i<=n;++i)
		scanf("%lld",&a[i]),pfx[i]=pfx[i-1]+a[i];
		for (suf[n+1]=0,i=n,ans=-1e18,SEG.build();i>=0;--i)
		{
			SEG.modify(i,n,a[i]); int pos=SEG.query();
			if (pos>n) suf[i]=pfx[n]-pfx[i-1]; else suf[i]=suf[pos+1];
			if (pfx[i]+suf[i+1]>ans) ans=pfx[i]+suf[i+1],k=pfx[i];
		}
		printf("%lld\n",k);
	}
	return 0;
}

E. Boxes and Balls

好题,不过据说\(O(n^2k)\)大暴力也能水过的说

首先发现两个重要性质,其一是不论怎么操作所有球的相对顺序不会变化

其二是如果我们强制统计某个状态时要满足移动的步数最小,则算出此时移动\(1,2,3,\cdots,k\)步的状态数,最后把所有和\(k\)奇偶性相同的状态加起来就是答案了

这样的原因是当用较少的步数得到一个状态后,可以随便找一个位置来回移动消耗偶数次步数而保证状态不变

转化成最小步数之后就有很好的性质了,我们设初始时\(1\)的位置为\(s_1,s_2,\cdots,s_m\),某个目标状态中\(1\)的位置为\(t_1,t_2,\cdots,t_m\),则最小步数为\(\sum_{j=1}^m |s_j-t_j|\)

由此我们很容易写出一个暴力DP的做法,设\(f_{i,j,k}\)表示已经处理了最终状态的前\(i\)个位置,其中放了\(j\)个球,总最小操作步数为\(k\)的方案数,转移就分要不要在位置\(i\)放球即可

据说用滚动数组压一下空间就能跑过,但这题其实有更好的做法

考虑令\(pfx_i\)表示初始时\(a_i\)的前缀和,则有结论最终状态的这一位的前缀和\(pfx'_i\)可能的取值为\([pfx_i-O(\sqrt k),pfx_i+O(\sqrt k)]\)

原因其实很简单,因为不管是把\(1\)移入还是移出,操作\(t\)个数所需的步数最少都是\(\frac{t(t+1)}{2}\),而总操作数只有\(k\),因此取\(S=O(\sqrt k)=100\)就没问题了

然后再用上面的DP,总复杂度\(O(nk\sqrt k)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1505,mod=1e9+7,S=100;
int n,k,a[N],pfx[N],f[N][N],pos[N],cnt,ans;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j,t; for (scanf("%d%d",&n,&k),i=1;i<=n;++i)
	if (scanf("%d",&a[i]),pfx[i]=pfx[i-1]+a[i],a[i]) pos[++cnt]=i;
	for (f[0][0]=i=1;i<=n;++i)
	{
		int l=max(1,pfx[i]-S),r=min(i,min(cnt,pfx[i]+S));
		for (j=r;j>=l;--j)
		{
			int cur=abs(pos[j]-i);
			for (t=k;t>=cur;--t) (f[j][t]+=f[j-1][t-cur])%=mod;
		}
	}
	for (i=k;i>=0;i-=2) (ans+=f[cnt][i])%=mod;
	return printf("%d",ans),0;
}

Postscript

之前欠下的CF和Atcoder有时间再补吧,现在手头还有学校的字符串和几何专题要写

posted @ 2023-07-01 15:35  空気力学の詩  阅读(140)  评论(2编辑  收藏  举报