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\)支这类花,一个小女孩想用一些花装饰她的蛋糕,她挑选的花能满足以下条件:
- 总花瓣数不超过\(m\)
- 任意两朵花花瓣数量差不超过\(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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】