codeforces div_2 961 题解报告

codeforces div_2 936 题解报告

比赛链接:https://codeforces.com/contest/1995

A.Diagonals

题目翻译

给定一个边长为\(n\)的正方形,给定\(k\),要往正方形选\(k\)个点,\(x+y\)相同的点构成对角线,问至少要几条对角线才能装下\(k\)个点。

  • 时限1s,空间限制256MB
  • \(1\le n\le 100,0\le k\le n^2\)

解法

先填长的对角线再填短的。

点我查看代码

#include<bits/stdc++.h> 
#define ll long long 
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
typedef int LL;
const signed maxn=(signed)1e6+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
signed main()
	{	 
		LL T=Read();
		while (T--){
			int n=Read(),k=Read();
			if(k==0){
				printf("0\n");continue;
			}
			int c=1;
			k-=n;int now=n-1;
			while(k>0&&now>0){
				if(k>=now) k-=now,++c;
				if(k>=now) k-=now,++c;
				--now;
			}
			printf("%d\n",c);
		}
		return 0;
	}	

B.Bouquet(Hard version)

题目翻译

给定\(n\)种花,每一种花有\(a_i\)片花瓣,花店有\(c_i\)支这类花,一个小女孩想用一些花装饰她的蛋糕,她挑选的花能满足以下条件:

  1. 总花瓣数不超过\(m\)
  2. 任意两朵花花瓣数量差不超过\(1\)

问满足以上条件,小女孩买的花的花瓣数之和最大多少?

  • 时限1.5s,空间限制256MB
  • \(n\le 2\cdot 10^5,1\le m\le 10^{18},1\le a_i,c_i\le 10^9\)

解法

如果选的所有花\(a_i\)都相同,那么很简单,不超过\(m\)的条件下直接尽可能多的选就行。
如果选择两种花,那么设价值为\(v-1,v\),事实上有统一的策略达到最优。先尽可能的选\(v-1\)的花,再尽可能选\(v\)的花,如果没有完全到达\(m\),尝试把\(v-1\)换为\(v\),直到到达\(m\),或者无法交换为止。

点我查看代码
#include<bits/stdc++.h> 
#define ll long long 
#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
#define pii std::pair<int,int>
typedef int LL;
const signed maxn=(signed)1e6+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
pii fr[maxn];
signed main()
	{	 
		LL T=Read();
		while (T--){
			int n=Read(),m=Read();
			for(int i=1;i<=n;++i){
				fr[i].first=Read();
			}
			for(int i=1;i<=n;++i){
				fr[i].second=Read();
			}
			std::sort(fr+1,fr+1+n);
			int pc=0,pv=-2;
			int ans=0;
			for(int i=1;i<=n;++i){
				int nowc=fr[i].second,nowv=fr[i].first;
				while(i<n&&fr[i].first==fr[i+1].first) 
					nowc+=fr[++i].second;
				ans=std::max(ans,std::min(nowc,m/nowv)*nowv);
				if(nowv==pv+1){
					int mc=std::min(pc,m/pv);
					int mx=mc*pv;
					int delta=m-mc*pv;
					int mc2=std::min(nowc,delta/nowv);
					delta-=mc2*nowv;
					delta-=std::min({delta,mc,nowc-mc2});
					ans=std::max(ans,m-delta);
				}
				pv=nowv;pc=nowc;
			}
			printf("%lld\n",ans);
		}
		return 0;
	}	


C.Squaring

题目翻译

给定一个大小为\(n\)的数组\(a\),可以做以下操作任意次.

  • 选择一个\(i\in[1,n]\),将\(a_i\)\(a_i^2\)替代

问至少要多少次能使数组单调不减。

  • 时限2s,空间限制512MB
  • \(1\le n\le 2\cdot 10^5,1\le a_i\le 10^6\)

解法

丑陋的对数做法。取一次对数,设\(d_i=\log_2 a_i\),\(a_i\)平方就是\(d_i\)\(2\),设每一位操作\(c_i\)次,那么有\(2^{c_{i-1}}\times d_{i-1}\le 2^{c+i}\times d_i\),移项得\(2^{c_i-c_{i-1}}\ge \frac{d_{i-1}} {d_i}\),再取一次对数得\(c_i-c_{i-1}\ge \log_2\frac{d_{i-1}} {d_i}\),因为\(c_i\)为非负整数,所以\(c_i=max(0,\lceil c_{i-1}+\log_2\frac{d_{i-1}} {d_i}\rceil)\)
代码套公式就行,唯一注意的是考虑精度,向上取整ceil要-eps。

点我查看代码
#include<bits/stdc++.h> 
#define ll long long 
#define double long double
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
typedef int LL;
const signed maxn=(signed)1e6+5;
const double eps=1e-14;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
int ar[maxn];
double dr[maxn];
ll cr[maxn];
signed main()
	{	 
		LL T=Read();
		A:while (T--){
			int n=Read();
			for(int i=1;i<=n;++i){
				ar[i]=Read();
			}
			for(int i=1;i<=n;++i){
				dr[i]=std::log2(ar[i]);cr[i]=0;
			}
			bool f=1;
			ll ans=0;
			// for(int i=1;i<=n;++i){
			// 	printf("%.2lf%c",dr[i]," \n"[i==n]);
			// }
			for(int i=2;f&&i<=n;++i){
				//printf("%.2lf\n",std::log2(dr[i-1]/dr[i]));
				if(ar[i-1]!=1&&ar[i]==1){
					printf("-1\n");f=0;
				}
				else{
					cr[i]=std::max(0,(int)ceil(cr[i-1]+std::log2(dr[i-1]/dr[i])-eps));
					ans+=cr[i];
				}
			}
			if(!f) continue;
			printf("%lld\n",ans);
		}
		return 0;
	}	
	

有队员使用不用double的做法,实在是太帅,这里给一个链接。
https://codeforces.com/contest/1995/submission/272134033

D.Cases

题目翻译

给定长为\(n\)的字符串,字符集由前\(c\)个大写字母组成。现要求把字符串拆分为若干长度不超过\(k\)的子串,要求最小化作为子串结尾字母的数量。

  • 时限2s,空间限制256MB
  • \(1\le n,k\le 2^{18},1\le c\le 18\)

解法

典中典带\(O(c\cdot2^c)\)复杂度的题目。
根据每一个字母是否可以作为结尾字母,可以分为\(2^c\)个状态。我们要考虑那些状态可行,哪些不可行。
不可行其实就是存在一个起始位置,包括自己向后扫描\(k\)位没有找到可以作为结尾的字母。
然后原串结尾字母一定要是可结尾字母。所以可以得到状态不可行的充要条件。
一个状态不可行,当且仅当原串尾字母不可结尾,或者,存在长度为\(k\)的连续子串,其包含的所有字母都不是可结尾字母。
然后,我们只要考虑长度恰好为\(k\)的子串即可,因为极小的不合法状态一定在长度最短的子串中出现。之后自下而上\(dp\)一下就可以得到所有状态的合法性。

点我查看代码
#include<bits/stdc++.h> 
#define ll long long 
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
typedef int LL;
const signed maxn=(signed)2e6+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
bool sta[maxn];
char s[maxn];
int bkt[30];
signed main()
	{	 
		LL T=Read();
		while (T--){
			int n=Read(),c=Read(),k=Read();
			for(int i=0;i<26;++i) bkt[i]=0;
			int mx=(1<<c);
			for(int i=0;i<mx;++i)
				sta[i]=1;
			scanf("%s",s+1);
			for(int i=1;i<=k;++i){
				++bkt[s[i]-'A'];
			}
			int ss=0;
			for(int j=0;j<c;++j) if(bkt[j]) ss|=(1<<j);
			//printf("ss=%d\n",ss);
			sta[ss]=0;
			for(int i=k+1;i<=n;++i){
				--bkt[s[i-k]-'A'];
				++bkt[s[i]-'A'];
				int ss=0;
				for(int j=0;j<c;++j) if(bkt[j]) ss|=(1<<j);
                //1代表不作为结尾字母
				sta[ss]=0;
				//printf("ss=%d\n",ss);
			}
			sta[1<<(s[n]-'A')]=0;
			//printf("ss=%d\n",1<<(s[n]-'A'));
			int ans=c;
			for(int i=1;i<mx;++i){
				int c0=0;
				for(int j=c-1;~j;--j){
					//printf("%1d",(i>>j)&1);
					if((i>>j)&1) sta[i]&=sta[i^(1<<j)];
					else ++c0;
				}
				//printf(":%d\n",sta[i]);
				if(sta[i]) ans=std::min(ans,c0);
			}
			printf("%d\n",ans);
		}
		return 0;
	}	
	

E.Let Me Teach You a Lesson (Hard Version)

题目翻译

给定一个数\(n\),和长度为\(2n\)的数组\(a\),可以做以下操作任意次

  • 选择一个数\(i\in [1,n]\),交换\(a_i\)\(a_{i+n}\)

\(min(max_{i=1}^n(a_{2i-1}+a_{2i})-min_{i=1}^n(a_{2i-1}+a_{2i}))\)

  • 时限2s,空间限制256MB
  • \(1\le n\le 10^5,1\le a_i\le 10^9\)

感慨一下先!

E题其实也是一道很经典的题目,先不考虑合法性,分开考虑方便计算答案,再检验合法性。
然后维护合法性的线段树更是典中典,区间支持合并,就可以考虑上线段树维护,一般区间会保留左右端的信息和区间内的信息,左右合并的时候把左区间的右和右区间的左处理一下,变为区间内即可。

解法

首先,发现\(n\)为偶数很简单,选\(i\in[1,n]\),且\(1\)为奇数,\(a_i,a_{i+n},a_{i+1},a_{i+1+n}\)这四个数正好组成交换和求和两组,他们的交换和求和局限在这两组内,处理很简单,不多说。

然后是正式内容,\(n\)为奇数。
首先可以发现,每一个数都有一个可以交换的数和统计答案的相邻的数,不妨将可以交换的数和最后一起统计答案的数都连一条边,可以发现恰好构成一个环,那么我们可以重构数组的排序,使统计答案和交换数对都发生在相邻的数之间。
然后,我们以统计答案的一对数作为一个统计区间,发现交换发生在相邻两个区间之间,左侧整体最右边的数和右侧整体最左边的数交换。可以发现,将相邻两个区间合并,可以构成一个大区间,并且仍然满足上述性质。
那么我们考虑大小为\(2\)的极小区间的形态,根据左边是否交换和右边是否交换可以得到\(2*2\)的状态。那么总共有\(4n\)中可能的极小区间状态。我们按照区间中数对和的大小排序,然后最小值和最大值分别使用一个指针(双指针法),对于每一个最小值,我们找到最小的合法最大值,更新答案。
什么是合法最大值?首先,在最小值与最大值区间中,每一个极小区间的\(4\)个状态至少出现一个,并且存在一种选择,每一个区间选择一个状态,这些整体是不冲突的。

  • 冲突:显然一个交换会影响两个区间,但是每个区间单独考虑就会导致这种联系无法体现,可能会出现不合法的情况。比如两个相邻区间,左边区间选取与右侧交换,而右边区间选择不与左边交换,就产生了冲突。

如何在双指针的同时维护是否存在合法情况呢?
前面说了,相邻区间可以合并为大区间,也就是可以作区间合并,那么我们用\(2*2\)矩阵表示一个位置的\(4\)种状态,然后用线段树维护,每增加或减少一个极小区间状态就修改对应区间的状态矩阵,然后更新整一个线段树。每一次看线段树的根的矩阵就可以知道是否有冲突。
大致思想如此,具体细节都是典中典处理,看码即可。

点我查看代码

#include<bits/stdc++.h> 
#define ll long long 
#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f 
#define pii std::pair<int,int>
typedef int LL;
const signed maxn=(signed)2e5+5;
inline LL Read(){
	char ch=getchar();bool f=0;LL x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
int ar[maxn];
int match[maxn];
int br[maxn],top;
void pf(int *a,int l,int r){
	for(int i=l;i<=r;++i) printf("%lld%c",a[i]," \n"[i==r]);
}
int n,nn;
struct Mtx{
	bool a[2][2];
	Mtx(){
		a[0][0]=a[1][1]=a[1][0]=a[0][1]=0;
	}
	void clear(){
		a[0][0]=a[1][1]=a[1][0]=a[0][1]=0;
	}
}tr[maxn<<2];
Mtx operator *(const Mtx l,const Mtx r){
	Mtx c;
	for(int i=0;i<2;++i){
		for(int j=0;j<2;++j){
			c.a[i][j]=(l.a[i][0]&r.a[0][j])|(l.a[i][1]&r.a[1][j]);
		}
	}
	return c;
};
struct Desk{
	int pos,val;
	int l,r;//[0,1],left change,right change
	Desk()=default;
	Desk(int pos,int val,int l,int r):pos(pos),val(val),l(l),r(r){}
}desk[maxn<<2];
void build(int u,int l,int r){
	tr[u].clear();
	if(l==r) return;
	build(lson);build(rson);
}
void update(int u){
	tr[u]=tr[u<<1]*tr[u<<1|1];
}
int _l,_r;
bool _sta;
void _change(int u,int l,int r,int pos);
void change(Desk a,int sta){
	_sta=sta;_l=a.l;_r=a.r;
	_change(1,1,n,a.pos);
}
void _change(int u,int l,int r,int pos){
	if(l==r){
		tr[u].a[_l][_r]=_sta;return;
	}
	if(pos<=mid) _change(lson,pos);
	else _change(rson,pos);
	update(u);
}
int dcnt;
int get_ans(){
	std::sort(desk+1,desk+1+dcnt,[&](const Desk &a,const Desk &b){
		return a.val<b.val;
	});
	build(1,1,n);
	int pr=0,ans=2e9;
	for(int pl=1;pl<=dcnt;++pl){
		while(pr<dcnt&&!(tr[1].a[0][0]|tr[1].a[1][1])){
			//printf("++r\n");
			change(desk[++pr],1);
		}
		if(!(tr[1].a[0][0]|tr[1].a[1][1])) break;
		ans=std::min(ans,desk[pr].val-desk[pl].val);
		change(desk[pl],0);
		//printf("++l\n");
	}
	return ans;

}
signed main()
	{	 
		LL T=Read();
		while (T--){
			n=Read();
			nn=(n<<1);
			for(int i=1;i<=nn;++i){
				ar[i]=Read();
			}
			if(n==1){
				printf("0\n");continue;
			}
			if(n%2==0){
				int mx=0,mn=(ll)2e9;
				for(int i=1;i<=n;i+=2){
					if(ar[i]>ar[i+n]&&ar[i+1]>ar[i+1+n]){
						std::swap(ar[i],ar[i+n]);
					}
					else if(ar[i]<ar[i+n]&&ar[i+1]<ar[i+1+n]){
						std::swap(ar[i],ar[i+n]);
					}
				}
				for(int i=1;i<=n;++i){
					int sum=ar[i*2-1]+ar[i*2];
					mx=std::max(mx,sum);
					mn=std::min(mn,sum);
				}
				printf("%lld\n",mx-mn);continue;
			}
			else{
				for(int i=1;i<=nn;++i){
					if(i&1) match[i]=i+1;
					else match[i]=i-1;
				}
				int now=1;				
				top=0;
				br[++top]=ar[2];br[++top]=ar[1];
				while(top<nn){
					now=(now+n-1)%nn+1;
					br[++top]=ar[now];
					now=match[now];
					br[++top]=ar[now];
				}
				br[0]=br[top];br[top+1]=br[1];br[top+2]=br[2];

				//pf(br,1,top);
				dcnt=0;
				for(int i=1;i<=n;++i){
					int A=br[i*2-2];
					int B=br[i*2-1];
					int C=br[i*2];
					int D=br[i*2+1];
					desk[++dcnt]=Desk(i,B+C,0,0);
					desk[++dcnt]=Desk(i,A+C,1,0);
					desk[++dcnt]=Desk(i,B+D,0,1);
					desk[++dcnt]=Desk(i,A+D,1,1);
				}
				printf("%lld\n",get_ans());
			}
		}
		return 0;
	}	

posted @   LOOP_0  阅读(165)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示