CF 刷题计划

0x00 前言

感觉最近需要提升一下思维水平,于是就准备刷CF的题目。
刷题内容:CF1300开始,评分大概在某个区间内。

为了防止代码过长代码只给出核心部分,缺省源和数组大小(maxnmaxm 的定义)请自行补上。

0x01 CF1300B

难度:\(*1000\)
不难发现,对于任意的一个 \(i\le n\),能够作为匹配的 \(a_j-a_i\)\(j\) 都大于 \(n+1\),这样只需要将原序列排序,答案就为 \(a_{n+1}-a_n\),算法复杂度 \(O\left(n\log n\right)\),瓶颈在于排序。

int n,nn,a[maxn];
void work(){
	n=read(); nn=n<<1; int i,j; for(i=1;i<=nn;i++) a[i]=read(); sort(a+1,a+nn+1);
	print(a[n+1]-a[n]); pc('\n'); return;
}

0x02 CF1300C/CF1299A

难度:\(*1500\)
按位计算可以证明 (x|y)-yx&(~y) 是等价的,所以只需要算出 ~a[i] 的前缀和和后缀和,扫一次求最大值就可以了,算法复杂度 \(\Theta\left(n\right)\)

int n,a[maxn],pre[maxn],suf[maxn],maxx,ans;
int main(){
	n=read(); int i; for(i=1;i<=n;i++) a[i]=read(); if(n==1){ print(a[1]); return 0; }
	pre[0]=~0; for(i=1;i<=n;i++) pre[i]=pre[i-1]&(~a[i]);
	suf[n+1]=~0; for(i=n;i>=1;i--) suf[i]=suf[i+1]&(~a[i]);
	maxx=-1; for(i=1;i<=n;i++) if((pre[i-1]&a[i]&suf[i+1])>maxx) maxx=pre[i-1]&a[i]&suf[i+1],ans=i;
	print(a[ans]); for(i=1;i<=n;i++) if(i^ans) pc(' '),print(a[i]); return 0;
}

0x03 CF1300D/CF1299B

难度:\(*1800\)
分析一下样例,不难发现就是判断所给出的多边形是否是中心对称即可。因此如果 \(n\) 为奇数,那么就显然不是,否则记 \(k=\frac{n}{2}\),判断对于任意的 \(1\le i \le k\)\(x_i+x_{i+k}\)\(y_i+y_{i+k}\) 都是否为定值即可,算法复杂度 \(\Theta\left(n\right)\)

int n,nn,x[maxn],y[maxn],sx,sy;
int check(){
	if(n&1) return 0; int i; nn=n>>=1; sx=x[1]+x[1+nn]; sy=y[1]+y[1+nn];
	for(i=2;i<=nn;i++) if(sx!=x[i]+x[i+nn]||sy!=y[i]+y[i+nn]) return 0;
	return 1;
}
int main(){
	n=read(); int i; for(i=1;i<=n;i++) x[i]=read(),y[i]=read();
	if(check()) puts("YES"); else puts("NO"); return 0;
}

0x04 CF1300E/CF1299C

难度:\(*2100\)
观察样例三,不难发现答案单调不降(可以用反证法证明),因此使用单调栈维护。栈中存储一段元素的和与区间,如果栈顶区间的平均值比栈顶下面的元素小,那么就合并栈顶两个元素。最后输出即可,算法复杂度 \(\Theta\left(n\right)\)
注意比较平均值的时候不要用来除(而是乘),还要开 long long

int n,a[maxn];
struct JTZ{
	ll len,sum;
	bool operator < (const JTZ x) const { return this->sum*x.len<x.sum*this->len; }
	JTZ operator + (const JTZ x) const { return (JTZ){this->len+x.len,this->sum+x.sum}; }
}data[maxn]; int top; db ave;
int main(){
	n=read(); int i,j; for(i=1;i<=n;i++) a[i]=read();
	for(i=1;i<=n;i++){
		top++; data[top].sum=a[i],data[top].len=1;
		while(top>=2&&data[top]<data[top-1]) data[top-1]=data[top-1]+data[top],top--;
	}
	for(i=1;i<=top;i++){
		ave=data[i].sum*1.0/data[i].len;
		for(j=1;j<=data[i].len;j++) printf("%0.15lf\n",ave);
	}
	return 0;
}

0x05 CF1301B

难度:\(*1500\)
不难发现答案满足二分性质,所以直接二分就可以了。check 函数只需要扫一次得到 \(k\) 取值的最大值和最小值,然后判断最大值是否大于等于最小值。算法复杂度为 \(\Theta\left(n\log a_i\right)\)

int n,maxd,a[maxn];
int check(int x){
	int kl=0,kr=1e9,i;
	for(i=1;i<=n;i++) if(a[i]==-1){
		if(a[i-1]!=-1&&i>1) kl=mmax(kl,a[i-1]-x),kr=mmin(kr,a[i-1]+x);
		if(a[i+1]!=-1&&i<n) kl=mmax(kl,a[i+1]-x),kr=mmin(kr,a[i+1]+x);
	} return kl<=kr;
}
void printans(int x){
	int kl=0,kr=1e9,i;
	for(i=1;i<=n;i++) if(a[i]==-1){
		if(a[i-1]!=-1&&i>1) kl=mmax(kl,a[i-1]-x),kr=mmin(kr,a[i-1]+x);
		if(a[i+1]!=-1&&i<n) kl=mmax(kl,a[i+1]-x),kr=mmin(kr,a[i+1]+x);
	} print(x),pc(' '),print(kl),pc('\n'); return;
}
void work(){
	n=read(); int i; for(i=1;i<=n;i++) a[i]=read();
	maxd=0; for(i=1;i<n;i++) if(a[i]!=-1&&a[i+1]!=-1) maxd=mmax(maxd,mabs(a[i+1]-a[i]));
	int l=maxd-1,r=1e9+1,mid;
	while(l+1<r){ mid=(l+r)>>1; if(check(mid)) r=mid; else l=mid; }
	printans(r); return;
}

0x06 CF1301C

难度:\(*1800\)
原问题比较复杂,所以我们需要简化问题。题目让我们求的是有 \(1\) 的区间的最大值,不难发现如果序列长度为 \(n\) 的时候总区间数为 \(\frac{n\times(n-1)}{2}\),所以我们只需要求出全是 \(0\) 的区间的最小值,然后用总的区间数去减就可以了。显然我们需要把 \(0\) 尽量平均分成 \(m+1\) 段,计算答案即可。复杂度 \(\Theta\left(1\right)\)

ll n,m,a,b,cnt1,cnt2;
void work(){
	n=read(); m=read(); a=(n-m)/(m+1); b=a+1; cnt2=(n-m)%(m+1); cnt1=m+1-cnt2;
	print(n*(n+1)/2-cnt1*a*(a+1)/2-cnt2*b*(b+1)/2),pc('\n');
}

0x07 CF1301D

难度:(\(*2000\)
显然我们只需要找出任意一条欧拉回路即可。

大概就是这个样子。具体的过程如下:

  1. 向下到底,然后向上到顶
  2. 向右
  3. 向下,向左,向右
  4. 重复 3 直至到底
  5. 向上到顶
  6. 重复2-5直至到右上角
  7. 向左回到起点

我们发现操作数在 \(O(m)\) 级别。
我们只需要输出前 \(k\) 步即可。
代码:

int n,m,k;
int cnt,f[maxn],g[maxn];
void add(int x,int y){ if(x<1) return; f[++cnt]=x; g[cnt]=y; return; }
// U 1  D 2  L 3  R 4  DLR 5
int main(){
	n=read(); m=read(); n--; m--; k=read();
	if(k>4*n*m+2*n+2*m){ puts("NO"); return 0; } else puts("YES");
	if(k<=n){ pc('1'),pc('\n'),print(k),pc(' '),pc('D'); return 0; }
	if(k<=2*n){ pc('2'),pc('\n'),print(n),pc(' '),pc('D'),pc('\n'),print(k-n),pc(' '),pc('U'); return 0; }
	add(n,2); add(n,1); k-=n*2;
	int i; for(i=1;i<=m;i++){
		if(!k) break; add(1,4); k--;
		if(k<=n*3){
			if(k>=3) add(k/3,5);
			switch(k%3){
				case 0: break;
				case 1: add(1,2); break;
				case 2: add(1,2),add(1,3); break;
			} k=0; break;
		}
		add(n,5); k-=3*n;
		if(k<=n){ add(k,1); k=0; break; }
		add(n,1); k-=n;
	} add(k,3); print(cnt),pc('\n');
	for(i=1;i<=cnt;i++){
		print(f[i]),pc(' ');
		switch(g[i]){
			case 1: pc('U'),pc('\n'); break;
			case 2: pc('D'),pc('\n'); break;
			case 3: pc('L'),pc('\n'); break;
			case 4: pc('R'),pc('\n'); break;
			case 5: pc('D'),pc('L'),pc('R'),pc('\n'); break;
		}
	} return 0;
}

0x08 CF1303D

难度:\(*1900\)
考虑对 \(m\) 进行二进制拆分。
从低位到高位考虑,如果当当前位不存在就去暴力查找最小的有的一位然后分解,这样复杂度就是 \(\Theta(n)\) 的,如果从低位到高位的话就可以卡成 \(O(n^2)\) 的了。
正确性证明和时间复杂度证明略。(请读者自行完成)

int js(int);//js(x) 返回 log2(x),这里因为用 switch-case 语句暴力求,代码过长,会影响阅读体验,所以不展示
int n,x,ans,t[maxn]; ll m,sum;
void work(){
	m=read(); n=read(); Me(t,0); sum=0; ans=0; int i,j;
	for(i=1;i<=n;i++) x=read(),sum+=x,t[js(x)]++;
	if(sum<m){ puts("-1"); return; } if(sum==m){ puts("0"); return; }
	for(i=0;i<31;i++){
		if(!t[i]&&(m&(1<<i))){ j=i; while(!t[j]) j++,ans++; t[j]--; for(j=j-1;j>=i;j--) t[j]++; }
		if(m&(1<<i)) t[i]--; t[i+1]+=(t[i]>>1);
	} print(ans),pc('\n'); return; 
}

0x09 CF1304E

难度:\(*2000\)
显然我们只要找到一条从 \(a\)\(b\) 长度为 \(dis\) 的路径,满足 \(k\ge dis\)\(k-dis\) 为偶数。
\(a\)\(b\) 显然有 \(3\) 条路径可以求出: \(a \to b\)\(a \to x \to y \to b\)\(a \to y \to x \to b\)
求树上任意两点的简单路径长度只要求出两个点的最近公共祖先就好了。

int n,logn,T,u,v,x,y,k;
int head[maxn],to[maxn<<1],nex[maxn<<1],kkk;
#define add(x,y) nex[++kkk]=head[x]; head[x]=kkk; to[kkk]=y
int fa[maxn][20],dep[maxn];
void dfs(int x,int pre){
	int i; for(i=head[x];i;i=nex[i]) if(to[i]!=pre){
		dep[to[i]]=dep[x]+1; fa[to[i]][0]=x; dfs(to[i],x); } return;
}
void init(){
	dep[1]=1; dfs(1,-1); int i,j;
	for(j=1;j<=logn;j++) for(i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; return;
}
int lca(int x,int y){
	if(dep[x]<dep[y]) mswap(x,y); int i;
	for(i=logn;i>=0;i--) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
	for(i=logn;i>=0;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
	if(x!=y) x=fa[x][0]; return x;
}
int dis(int x,int y){ int fa=lca(x,y); return dep[x]-dep[fa]+dep[y]-dep[fa]; }
void work(){
	x=read(); y=read(); u=read(); v=read(); k=read(); int tmp;
	tmp=dis(u,v); if(mabs(tmp-k)%2==0&&tmp<=k){ puts("YES"); return; }
	tmp=dis(u,x)+dis(y,v)+1; if(mabs(tmp-k)%2==0&&tmp<=k){ puts("YES"); return; }
	tmp=dis(u,y)+dis(x,v)+1; if(mabs(tmp-k)%2==0&&tmp<=k){ puts("YES"); return; }
	puts("NO"); return;
	//u=read(); v=read(); print(dis(u,v)),pc('\n'); return;
}
int main(){
	n=read(); logn=log2(n)+1; int i; for(i=1;i<n;i++){ u=read(); v=read(); add(u,v); add(v,u); }
	init(); T=read(); while(T--) work(); return 0;
}

0x0A CF1307D

难度:\(*1900\)
设点 \(1\) 到点 \(i\) 的最短路为 \(dis1_i\)\(n\) 号点到点 \(i\) 的最短路为 \(dis2_i\)
假设从路径为 \(1\to u \to v \to n\),连接点 \(u,v\)。如果 \(u\) 给定,那么最短路就是 \(\min(dis1_n,dis1_u+dis2_v+1)\),此时需要 \(dis1_v>dis1_u\) 并且 \(dis1_v-dis_u\) 最小。
所以只要跑最短路 + 排序,然后扫一次就可以了。
注意答案小于等于 \(dis1_n\)
代码:

int n,m,k,u,v,x,l,r,mid,s[maxn],dis1[maxn],dis2[maxn],ans;
int head[maxn],to[maxn<<1],nex[maxn<<1],kkk;
#define add(x,y) nex[++kkk]=head[x]; head[x]=kkk; to[kkk]=y
queue<int> q,E;
void SPFA(int s,int *dis){
	int cur,i; for(i=1;i<=n;i++) dis[i]=INF; q=E; q.push(s); dis[s]=0; 
	while(!q.empty()){
		cur=q.front(); q.pop();
		for(i=head[cur];i;i=nex[i])
			if(dis[to[i]]>dis[cur]+1){ dis[to[i]]=dis[cur]+1; q.push(to[i]); }
	} return;
}
int cmp(int x,int y){ return dis1[x]<dis1[y]; }
int main(){
	n=read(); m=read(); k=read(); int i; for(i=1;i<=k;i++) s[i]=read();
	for(i=1;i<=m;i++){ u=read(); v=read(); add(u,v); add(v,u); } SPFA(1,dis1); SPFA(n,dis2);
	sort(s+1,s+k+1,cmp); for(i=1;i<k;i++) ans=mmax(dis1[s[i]]+dis2[s[i+1]]+1,ans);
	print(mmin(ans,dis1[n])); return 0;
}

0x0B CF1311D

难度:\(*2000\)
枚举 \(A\),然后枚举 \(A\) 的倍数 \(B\),然后枚举 \(B\) 的倍数 \(C\)。注意不是枚举到 \(c\) 的大小。
我们发现单次复杂度是 \(\Theta(c\log^2c)\),能过。
代码:

int a,b,c,x,y,z,ans;
void work(){
	a=read(); b=read(); c=read(); int i,j,k; ans=INF;
	for(i=1;i<=c;i++) for(j=i;j<=c+i;j+=i) for(k=j;k<=c+j;k+=j)
		if(mabs(i-a)+mabs(j-b)+mabs(k-c)<ans) ans=mabs(i-a)+mabs(j-b)+mabs(k-c),x=i,y=j,z=k;
	print(ans),pc('\n'),print(x),pc(' '),print(y),pc(' '),print(z),pc('\n'); return;
}

0x0C CF1311E

差点不会Div3E
设到所有点根节点的距离和为 \(d\)。显然 \(d\) 最大的时候是一条链,最小的时候是满二叉树。
考虑从 \(d\) 最小的时候开始调整,全部放到一条链上面。如果 \(d\) 大于给定的值就把链的末端提上来就好了。
代码:

int n,m,now,maxx,maxy,fa[maxn],dep[maxn];
void work(){
	n=read(); m=read(); now=0; int i,j; dep[1]=0;
	for(i=2;i<=n;i++) fa[i]=(i>>1),dep[i]=dep[i>>1]+1,now+=dep[i]; if(now>m){ puts("NO"); return; }
	if(now==m){ puts("YES"); for(i=2;i<=n;i++) print(fa[i]),pc(' '); pc('\n'); return; }
	i=1; while((i<<1)<=n) i<<=1; maxx=dep[i]; maxy=i;
	for(i=n;i>=1;i--) if(i&(i-1)){
		fa[i]=maxy; now+=maxx+1-dep[i]; dep[i]=++maxx; maxy=i;
		if(now>=m){
			for(j=1;j<=now-m;j++) fa[i]=fa[fa[i]];
			puts("YES"); for(j=2;j<=n;j++) print(fa[j]),pc(' '); pc('\n'); return;
		}
	} puts("NO"); return;
}

0x0D CF1311F

难度:\(*1900\)
显然对于两个点 \(i,j\) 满足 \(x_i<x_j\) 并且 \(v_i\le v_j\) 这样才能对答案产生大小为 \(x_j-x_i\) 的贡献。
显然这是一个二位偏序问题,只要把 \(v\) 离散化一下,按照 \(x_i\) 升序排序,树状数组需要维护 \(x_i\) 的和以及点的个数。

int n;
struct JTZ{ int x,v,num,s; }a[maxn];
int cmp1(JTZ x,JTZ y){ return x.v<y.v; } int cmp2(JTZ x,JTZ y){ return x.x<y.x; }
ll c1[maxn],c2[maxn],ans;
#define lowbit(x) (x&-x)
void add(int x,int y){
	while(x<=n){
		c1[x]+=y; c2[x]++;
		x+=lowbit(x);
	} return;
}
ll query(int x,int y){
	ll cnt1,cnt2; cnt1=cnt2=0; while(x){
		cnt1+=c1[x]; cnt2+=c2[x];
		x-=lowbit(x);
	} return y*cnt2-cnt1;
}
int main(){
	n=read(); int i; for(i=1;i<=n;i++) a[i].x=read(),a[i].num=i; for(i=1;i<=n;i++) a[i].v=read();
	sort(a+1,a+n+1,cmp1); a[1].s=1; for(i=2;i<=n;i++) a[i].s=a[i-1].s+(a[i].v!=a[i-1].v);
	sort(a+1,a+n+1,cmp2); for(i=1;i<=n;i++){ ans+=query(a[i].s,a[i].x); add(a[i].s,a[i].x); }
	print(ans); return 0;
}

0x0E CF1312E

难度:\(*2100\)
首先 \(\Theta\left(n^3\right)\) 区间 DP 计算出 \([l,r]\) 能否并成一个数字,如果可以那么 \(f(l,r)\) 就是并成的数字,否则为无穷大。
然后 \(\Theta\left(n^2\right)\) 线性 DP 计算出答案,记 \(g_i\) 为前 \(i\) 个的答案,然后枚举转移过来的区间即可。
复杂度 \(\Theta\left(n^3\right)\)

int n,f[maxn][maxn],g[maxn],a[maxn];
int main(){
	int i,j,k; n=read(); Me(f,0x3f); Me(g,0x3f); for(i=1;i<=n;i++) a[i]=read(),f[i][i]=a[i];
	for(i=n;i>=1;i--) for(j=i+1;j<=n;j++) for(k=i;k<j;k++)
		if(f[i][k]==f[k+1][j]&&f[i][k]!=0x3f3f3f3f) f[i][j]=f[i][k]+1;
	g[0]=0; for(i=1;i<=n;i++) for(j=0;j<i;j++) if(f[j+1][i]!=0x3f3f3f3f) g[i]=mmin(g[j]+1,g[i]);
	print(g[n]); return 0;
}
posted @ 2022-03-08 21:04  jiangtaizhe001  阅读(221)  评论(0编辑  收藏  举报